Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
A .classpath
1
<?xml version="1.0" encoding="UTF-8"?>
2
<classpath>
3
	<classpathentry kind="src" path="src/main/java">
4
		<attributes>
5
			<attribute name="FROM_GRADLE_MODEL" value="true"/>
6
		</attributes>
7
	</classpathentry>
8
	<classpathentry kind="src" path="src/main/resources">
9
		<attributes>
10
			<attribute name="FROM_GRADLE_MODEL" value="true"/>
11
		</attributes>
12
	</classpathentry>
13
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
14
		<accessrules>
15
			<accessrule kind="accessible" pattern="javafx/**"/>
16
		</accessrules>
17
	</classpathentry>
18
	<classpathentry exported="true" kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
19
	<classpathentry kind="output" path="bin"/>
20
</classpath>
121
A .gitattributes
1
# always use LF line endings
2
3
# ALL FILES:
4
#   Normalize line endings to LF on checkin and
5
#   prevent conversion to CRLF when the file is checked out.
6
7
* text eol=lf
8
9
10
# BINARY FILES:
11
#   Disable line ending normalize on checkin.
12
13
*.gif binary
14
*.jar binary
15
*.png binary
16
*.zip binary
117
A .gitignore
1
/bin/
2
/build/
3
/.gradle/
4
/gradle/
5
/.nb-gradle
6
/private
7
.nb-gradle-properties
8
scrivenvar.pro
19
A .project
1
<?xml version="1.0" encoding="UTF-8"?>
2
<projectDescription>
3
	<name>Markdown Writer FX</name>
4
	<comment></comment>
5
	<projects>
6
	</projects>
7
	<buildSpec>
8
		<buildCommand>
9
			<name>org.eclipse.jdt.core.javabuilder</name>
10
			<arguments>
11
			</arguments>
12
		</buildCommand>
13
		<buildCommand>
14
			<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
15
			<arguments>
16
			</arguments>
17
		</buildCommand>
18
	</buildSpec>
19
	<natures>
20
		<nature>org.eclipse.jdt.core.javanature</nature>
21
		<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
22
	</natures>
23
	<filteredResources>
24
		<filter>
25
			<id>1438449113801</id>
26
			<name></name>
27
			<type>26</type>
28
			<matcher>
29
				<id>org.eclipse.ui.ide.multiFilter</id>
30
				<arguments>1.0-projectRelativePath-matches-false-false-build</arguments>
31
			</matcher>
32
		</filter>
33
		<filter>
34
			<id>1438449113801</id>
35
			<name></name>
36
			<type>26</type>
37
			<matcher>
38
				<id>org.eclipse.ui.ide.multiFilter</id>
39
				<arguments>1.0-projectRelativePath-matches-false-false-.gradle</arguments>
40
			</matcher>
41
		</filter>
42
	</filteredResources>
43
</projectDescription>
144
A .settings/org.eclipse.buildship.core.prefs
1
GRADLE_BUILD_COMMANDS=org.eclipse.jdt.core.javabuilder
2
GRADLE_NATURES=org.eclipse.jdt.core.javanature
3
build.commands=org.eclipse.jdt.core.javabuilder
4
connection.arguments=
5
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
6
connection.gradle.user.home=null
7
connection.java.home=null
8
connection.jvm.arguments=
9
connection.project.dir=
10
derived.resources=.gradle,build
11
eclipse.preferences.version=1
12
natures=org.eclipse.jdt.core.javanature
13
project.path=\:
114
A .settings/org.eclipse.core.runtime.prefs
1
eclipse.preferences.version=1
2
line.separator=\n
13
A .settings/org.eclipse.jdt.core.prefs
1
eclipse.preferences.version=1
2
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
4
org.eclipse.jdt.core.compiler.compliance=1.8
5
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
6
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
7
org.eclipse.jdt.core.compiler.source=1.8
18
A .settings/org.eclipse.jdt.ui.prefs
1
eclipse.preferences.version=1
2
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
3
org.eclipse.jdt.ui.ignorelowercasenames=true
4
org.eclipse.jdt.ui.importorder=java;javafx;javax;org;com;
5
org.eclipse.jdt.ui.ondemandthreshold=99
6
org.eclipse.jdt.ui.staticondemandthreshold=99
7
sp_cleanup.add_default_serial_version_id=true
8
sp_cleanup.add_generated_serial_version_id=false
9
sp_cleanup.add_missing_annotations=true
10
sp_cleanup.add_missing_deprecated_annotations=true
11
sp_cleanup.add_missing_methods=false
12
sp_cleanup.add_missing_nls_tags=false
13
sp_cleanup.add_missing_override_annotations=true
14
sp_cleanup.add_missing_override_annotations_interface_methods=true
15
sp_cleanup.add_serial_version_id=false
16
sp_cleanup.always_use_blocks=true
17
sp_cleanup.always_use_parentheses_in_expressions=false
18
sp_cleanup.always_use_this_for_non_static_field_access=false
19
sp_cleanup.always_use_this_for_non_static_method_access=false
20
sp_cleanup.convert_functional_interfaces=false
21
sp_cleanup.convert_to_enhanced_for_loop=false
22
sp_cleanup.correct_indentation=false
23
sp_cleanup.format_source_code=false
24
sp_cleanup.format_source_code_changes_only=false
25
sp_cleanup.insert_inferred_type_arguments=false
26
sp_cleanup.make_local_variable_final=false
27
sp_cleanup.make_parameters_final=false
28
sp_cleanup.make_private_fields_final=true
29
sp_cleanup.make_type_abstract_if_missing_method=false
30
sp_cleanup.make_variable_declarations_final=true
31
sp_cleanup.never_use_blocks=false
32
sp_cleanup.never_use_parentheses_in_expressions=true
33
sp_cleanup.on_save_use_additional_actions=true
34
sp_cleanup.organize_imports=false
35
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
36
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
37
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
38
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
39
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
40
sp_cleanup.remove_private_constructors=true
41
sp_cleanup.remove_redundant_type_arguments=true
42
sp_cleanup.remove_trailing_whitespaces=true
43
sp_cleanup.remove_trailing_whitespaces_all=true
44
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
45
sp_cleanup.remove_unnecessary_casts=true
46
sp_cleanup.remove_unnecessary_nls_tags=false
47
sp_cleanup.remove_unused_imports=true
48
sp_cleanup.remove_unused_local_variables=false
49
sp_cleanup.remove_unused_private_fields=true
50
sp_cleanup.remove_unused_private_members=false
51
sp_cleanup.remove_unused_private_methods=true
52
sp_cleanup.remove_unused_private_types=true
53
sp_cleanup.sort_members=false
54
sp_cleanup.sort_members_all=false
55
sp_cleanup.use_anonymous_class_creation=false
56
sp_cleanup.use_blocks=false
57
sp_cleanup.use_blocks_only_for_return_and_throw=false
58
sp_cleanup.use_lambda=true
59
sp_cleanup.use_parentheses_in_expressions=false
60
sp_cleanup.use_this_for_non_static_field_access=false
61
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
62
sp_cleanup.use_this_for_non_static_method_access=false
63
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
64
sp_cleanup.use_type_arguments=false
165
A .travis.yml
1
language: java
2
3
jdk:
4
- oraclejdk8
5
6
# enable Java 8u45+, see https://github.com/travis-ci/travis-ci/issues/4042
7
addons:
8
  apt:
9
    packages:
10
      - oracle-java8-installer
11
os:
12
  - linux
13
14
# run in container
15
sudo: false
116
A CHANGES.md
1
# Change Log
2
3
## 0.6
4
5
- Bug fixes synchronized scrolling
6
- Added universal character encoding detection
7
- Removed options panel
8
- Decoupled Editor Tab and Preview Pane
9
10
## 0.5
11
12
- Added document processors for Markdown and Variables
13
- Simplified code base
14
- Added `Ctrl+Space` hot key for quick variable injection
15
- Replaced commonmark-java with flexmark
16
- Added generic CARETPOSITION into document to scroll preview pane
17
18
## 0.4
19
20
- Changed name to Scrivenvar
21
- Added hot-keys for variable mode and autocomplete
22
- Replaced pegdown with commonmark-java
23
- Started document processors to provide XSLT and variable dereferencing
24
25
## 0.3
26
27
- Changed name to Scrivendor
28
- Changed logo to match
29
- Started to implement service-oriented architecture
30
31
## 0.2
32
- RichTextFX (and dependencies) updated to version 0.6.10 (fixes bugs)
33
- pegdown Markdown parser updated to version 1.6
34
- Added five new pegdown 1.6 extension flags to Markdown Options tab
35
- Minor improvements
36
37
## 0.1
38
39
- Initial release
140
A CREDITS.md
1
Credits
2
===
3
4
  * Dave Jarvis: [Scrivenvar](https://github.com/DaveJarvis/scrivenvar/)
5
  * Karl Tauber: [Markdown Writer FX](https://github.com/JFormDesigner/markdown-writer-fx)
6
  * Tomas Mikula: [RichTextFX](https://github.com/TomasMikula/RichTextFX), [ReactFX](https://github.com/TomasMikula/ReactFX), [WellBehavedFX](https://github.com/TomasMikula/WellBehavedFX), [Flowless](https://github.com/TomasMikula/Flowless), and [UndoFX](https://github.com/TomasMikula/UndoFX)
7
  * Mikael Grev: [MigLayout](http://www.miglayout.com/)
8
  * Tom Eugelink: [MigPane](https://github.com/mikaelgrev/miglayout/blob/master/javafx/src/main/java/org/tbee/javafx/scene/layout/fxml/MigPane.java)
9
  * Vladimir Schneider: [flexmark](https://website.com)
10
  * Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx)
11
  * Apache Tika Team: [Apache Tika](https://tika.apache.org/)
112
A LICENSE
1
Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are met:
6
7
* Redistributions of source code must retain the above copyright
8
  notice, this list of conditions and the following disclaimer.
9
10
* Redistributions in binary form must reproduce the above copyright
11
  notice, this list of conditions and the following disclaimer in the
12
  documentation and/or other materials provided with the distribution.
13
14
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
125
A README.md
1
![Logo](images/logo64.png)
2
3
Scrivenvar
4
===
5
6
Word processing with variables.
7
8
![Screenshot](images/screenshot.png)
9
10
Features
11
---
12
13
* User-defined variables
14
* Recursive variable definitions
15
* Real-time document preview with variable substitution
16
* Platform independent (Windows, Linux, MacOS)
17
* Auto-insert variable names pressing `Control+Space`
18
19
Future Features
20
---
21
* Spell check
22
* Search and replace, with or without variables
23
* XML and XSL processing
24
* R integration using [Rserve](https://rforge.net/Rserve/)
25
* Re-organize variable names
26
27
Requirements
28
---
29
30
Download and install [Java 8u40](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
31
32
Installation
33
---
34
1. [Download](https://github.com/DaveJarvis/scrivenvar/releases) the latest zip archive and extract it to any folder.
35
1. Double-click `scrivenvar.jar` to start the application.
36
37
License
38
---
39
40
This software is licensed under the [BSD 2-Clause License](LICENSE).
141
A Scrivenvar.jfdproj
1
<?xml version="1.0" encoding="UTF-8"?>
2
<project>
3
  <entry key="classpath1" value="bin" />
4
  <entry key="classpath2" value="lib/fontawesomefx-8.9.jar" />
5
  <entry key="sourcefolders1" value="src/main/java" />
6
  <entry key="sourcefolders2" value="src/main/resources" />
7
  <node name="designer">
8
    <entry key="_i18nJavaSettingsEnabled" value="true" />
9
    <entry key="_i18nSettingsEnabled" value="true" />
10
    <entry key="i18n.externalizeexcludes1" value="com.scrivenvar.controls.EscapeTextField#escapeCharacters" />
11
    <entry key="i18n.externalizeexcludes2" value="com.scrivenvar.controls.WebHyperlink#uri" />
12
    <entry key="i18n.javagetstringformat" value="Messages.get(${key})" />
13
  </node>
14
  <node name="javacodegenerator">
15
    <entry key="_codeStyleSettingsEnabled" value="true" />
16
    <entry key="_generalSettingsEnabled" value="true" />
17
    <entry key="explicitimports" value="true" />
18
    <entry key="lineseparator" value="\n" />
19
  </node>
20
</project>
121
A USAGE.md
1
# Introduction
2
3
This document describes how to write documentation (technical or otherwise) using a master copy for generating a variety of output formats, such as: HTML pages, PDFs, and EPUBs. What's more, the document provides an overview of how to use variables and--for the unintimidated--leverage the power of R, a programming language.
4
5
# Software Requirements
6
7
Install Java, ConTeXt, Pandoc, R, and Lib V8. Then install the R packages knitr, yaml, and devtools, and pluralize by running the following commands:
8
9
    sudo su -
10
    apt-get install default-jre
11
    apt-get install context
12
    apt-get install pandoc
13
    apt-get install r
14
    apt-get install libv8-dev
15
    r
16
    url <- "http://cran.us.r-project.org"
17
    install.packages('knitr', repos=url)
18
    install.packages('yaml', repos=url)
19
    install.packages('devtools', repos=url)
20
    devtools::install_github("hrbrmstr/pluralize")
21
22
To exit R, press `Ctrl+d` or type `q()` followed by pressing `Enter`.
23
24
The required software packages are installed.
25
26
# Markdown
27
28
|Table|Table|
29
|---|---|
30
|Data|Data|
131
A build.gradle
1
version = '0.5'
2
3
apply plugin: 'java'
4
apply plugin: 'java-library-distribution'
5
apply plugin: 'application'
6
7
sourceCompatibility = 1.8
8
9
mainClassName = 'com.scrivenvar.Main'
10
11
repositories {
12
	jcenter()
13
}
14
15
compileJava {
16
  options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
17
}
18
19
dependencies {
20
  compile group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.7-M2'
21
	compile group: 'com.miglayout', name: 'miglayout-javafx', version: '5.0'
22
	compile group: 'de.jensd', name: 'fontawesomefx-fontawesome', version: '4.5.0'
23
  compile group: 'org.ahocorasick', name: 'ahocorasick', version: '0.3.0'
24
  compile group: 'com.vladsch.flexmark', name: 'flexmark', version: '0.6.1'
25
  compile group: 'com.vladsch.flexmark', name: 'flexmark-ext-gfm-tables', version: '0.6.1'
26
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.4'
27
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.4'
28
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.8.4'
29
  compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.8.4'
30
  compile group: 'org.yaml', name: 'snakeyaml', version: '1.17'
31
  compile group: 'com.googlecode.juniversalchardet', name: 'juniversalchardet', version: '1.0.3'
32
  compile group: 'commons-configuration', name: 'commons-configuration', version: '1.10'
33
}
34
35
jar {
36
	baseName = 'scrivenvar'
37
  
38
  from {
39
    (configurations.runtime).collect {
40
      it.isDirectory() ? it : zipTree(it)
41
    }
42
  }
43
    
44
	manifest {
45
		attributes 'Main-Class': mainClassName,
46
					'Class-Path': configurations.compile.collect { 'lib/' + it.getName() }.join(' ')
47
	}
48
}
49
50
distributions {
51
	main {
52
		baseName = 'scrivenvar'
53
		contents {
54
			from { ['LICENSE', 'README.md'] }
55
			into( 'images' ) {
56
				from { 'images' }
57
			}
58
		}
59
	}
60
}
161
A gradle.properties
11
A images/logo64.png
Binary file
A images/screenshot.png
Binary file
A settings.gradle
11
A src/main/java/com/scrivenvar/Constants.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
/**
31
 * @author White Magic Software, Ltd.
32
 */
33
public class Constants {
34
35
  /**
36
   * Prevent instantiation.
37
   */
38
  private Constants() {
39
  }
40
  
41
  public static final String BUNDLE_NAME = "com.scrivenvar.messages";
42
  public static final String SETTINGS_NAME = "/com/scrivenvar/settings.properties";
43
44
  public static final String STYLESHEET_PREVIEW = "com/scrivenvar/scene.css";
45
  public static final String STYLESHEET_EDITOR = "com/scrivenvar/editor/Markdown.css";
46
47
  public static final String LOGO_32 = "com/scrivenvar/logo32.png";
48
  public static final String LOGO_16 = "com/scrivenvar/logo16.png";
49
  public static final String LOGO_128 = "com/scrivenvar/logo128.png";
50
  public static final String LOGO_256 = "com/scrivenvar/logo256.png";
51
  public static final String LOGO_512 = "com/scrivenvar/logo512.png";
52
  
53
  /**
54
   * Separates YAML variable nodes (e.g., the dots in <code>$root.node.var$</code>).
55
   */
56
  public static final String SEPARATOR = ".";
57
  
58
  public static final String CARET_POSITION = "CARETPOSITION";
59
  public static final String MD_CARET_POSITION = "${" + CARET_POSITION + "}";
60
  public static final String XML_CARET_POSITION = "<![CDATA[" + MD_CARET_POSITION + "]]>";
61
}
162
A src/main/java/com/scrivenvar/FileEditorTab.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * Redistribution and use in source and binary forms, with or without
5
 * modification, are permitted provided that the following conditions are met:
6
 *
7
 *  o Redistributions of source code must retain the above copyright
8
 *    notice, this list of conditions and the following disclaimer.
9
 *
10
 *  o Redistributions in binary form must reproduce the above copyright
11
 *    notice, this list of conditions and the following disclaimer in the
12
 *    documentation and/or other materials provided with the distribution.
13
 *
14
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
package com.scrivenvar;
27
28
import com.scrivenvar.editor.EditorPane;
29
import com.scrivenvar.editor.MarkdownEditorPane;
30
import com.scrivenvar.service.Options;
31
import com.scrivenvar.service.events.AlertMessage;
32
import com.scrivenvar.service.events.AlertService;
33
import java.nio.charset.Charset;
34
import java.nio.file.Files;
35
import java.nio.file.Path;
36
import static java.util.Locale.ENGLISH;
37
import java.util.function.Consumer;
38
import javafx.application.Platform;
39
import javafx.beans.binding.Bindings;
40
import javafx.beans.property.BooleanProperty;
41
import javafx.beans.property.ReadOnlyBooleanProperty;
42
import javafx.beans.property.ReadOnlyBooleanWrapper;
43
import javafx.beans.property.SimpleBooleanProperty;
44
import javafx.beans.value.ChangeListener;
45
import javafx.event.Event;
46
import javafx.scene.Node;
47
import javafx.scene.control.Tab;
48
import javafx.scene.control.Tooltip;
49
import javafx.scene.input.InputEvent;
50
import javafx.scene.text.Text;
51
import org.fxmisc.undo.UndoManager;
52
import org.fxmisc.wellbehaved.event.EventPattern;
53
import org.fxmisc.wellbehaved.event.InputMap;
54
import org.mozilla.universalchardet.UniversalDetector;
55
56
/**
57
 * Editor for a single file.
58
 *
59
 * @author Karl Tauber and White Magic Software, Ltd.
60
 */
61
public final class FileEditorTab extends Tab {
62
63
  private final Options options = Services.load( Options.class );
64
  private final AlertService alertService = Services.load( AlertService.class );
65
66
  private EditorPane editorPane;
67
68
  /**
69
   * Character encoding used by the file (or default encoding if none found).
70
   */
71
  private Charset encoding;
72
73
  private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper();
74
  private final BooleanProperty canUndo = new SimpleBooleanProperty();
75
  private final BooleanProperty canRedo = new SimpleBooleanProperty();
76
  private Path path;
77
78
  FileEditorTab( final Path path ) {
79
    setPath( path );
80
    setUserData( this );
81
82
    this.modified.addListener( (observable, oldPath, newPath) -> updateTab() );
83
    updateTab();
84
85
    setOnSelectionChanged( e -> {
86
      if( isSelected() ) {
87
        Platform.runLater( () -> activated() );
88
      }
89
    } );
90
  }
91
92
  private void updateTab() {
93
    setText( getTabTitle() );
94
    setGraphic( getModifiedMark() );
95
    setTooltip( getTabTooltip() );
96
  }
97
98
  /**
99
   * Returns the base filename (without the directory names).
100
   *
101
   * @return The untitled text if the path hasn't been set.
102
   */
103
  private String getTabTitle() {
104
    final Path filePath = getPath();
105
106
    return (filePath == null)
107
      ? Messages.get( "FileEditor.untitled" )
108
      : filePath.getFileName().toString();
109
  }
110
111
  /**
112
   * Returns the full filename represented by the path.
113
   *
114
   * @return The untitled text if the path hasn't been set.
115
   */
116
  private Tooltip getTabTooltip() {
117
    final Path filePath = getPath();
118
119
    return (filePath == null)
120
      ? null
121
      : new Tooltip( filePath.toString() );
122
  }
123
124
  /**
125
   * Returns a marker to indicate whether the file has been modified.
126
   *
127
   * @return "*" when the file has changed; otherwise null.
128
   */
129
  private Text getModifiedMark() {
130
    return isModified() ? new Text( "*" ) : null;
131
  }
132
133
  /**
134
   * Called when the user switches tab.
135
   */
136
  private void activated() {
137
    // Tab is closed or no longer active.
138
    if( getTabPane() == null || !isSelected() ) {
139
      return;
140
    }
141
142
    // Switch to the tab without loading if the contents are already in memory.
143
    if( getContent() != null ) {
144
      getEditorPane().requestFocus();
145
      return;
146
    }
147
148
    // Load the text and update the preview before the undo manager.
149
    load();
150
151
    // Track undo requests (*must* be called after load).
152
    initUndoManager();
153
    initLayout();
154
    initFocus();
155
  }
156
157
  private void initLayout() {
158
    setContent( getScrollPane() );
159
  }
160
161
  private Node getScrollPane() {
162
    return getEditorPane().getScrollPane();
163
  }
164
165
  private void initFocus() {
166
    getEditorPane().requestFocus();
167
  }
168
169
  private void initUndoManager() {
170
    final UndoManager undoManager = getUndoManager();
171
172
    // Clear undo history after first load.
173
    undoManager.forgetHistory();
174
175
    // Bind the editor undo manager to the properties.
176
    modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) );
177
    canUndo.bind( undoManager.undoAvailableProperty() );
178
    canRedo.bind( undoManager.redoAvailableProperty() );
179
  }
180
181
  /**
182
   * Returns the index into the text where the caret blinks happily away.
183
   *
184
   * @return A number from 0 to the editor's document text length.
185
   */
186
  public int getCaretPosition() {
187
    return getEditorPane().getEditor().getCaretPosition();
188
  }
189
190
  /**
191
   * Returns true if the given path exactly matches this tab's path.
192
   *
193
   * @param check The path to compare against.
194
   *
195
   * @return true The paths are the same.
196
   */
197
  public boolean isPath( final Path check ) {
198
    final Path filePath = getPath();
199
200
    return filePath == null ? false : filePath.equals( check );
201
  }
202
203
  /**
204
   * Reads the entire file contents from the path associated with this tab.
205
   */
206
  private void load() {
207
    final Path filePath = getPath();
208
209
    if( filePath != null ) {
210
      try {
211
        getEditorPane().setText( asString( Files.readAllBytes( filePath ) ) );
212
      } catch( Exception ex ) {
213
        alert(
214
          "FileEditor.loadFailed.title", "FileEditor.loadFailed.message", ex
215
        );
216
      }
217
    }
218
  }
219
220
  /**
221
   * Saves the entire file contents from the path associated with this tab.
222
   *
223
   * @return true The file has been saved.
224
   */
225
  public boolean save() {
226
    try {
227
      Files.write( getPath(), asBytes( getEditorPane().getText() ) );
228
      getEditorPane().getUndoManager().mark();
229
      return true;
230
    } catch( Exception ex ) {
231
      return alert(
232
        "FileEditor.saveFailed.title", "FileEditor.saveFailed.message", ex
233
      );
234
    }
235
  }
236
237
  /**
238
   * Creates an alert dialog and waits for it to close.
239
   *
240
   * @param titleKey Resource bundle key for the alert dialog title.
241
   * @param messageKey Resource bundle key for the alert dialog message.
242
   * @param e The unexpected happening.
243
   *
244
   * @return false
245
   */
246
  private boolean alert(
247
    final String titleKey, final String messageKey, final Exception e ) {
248
    final AlertService service = getAlertService();
249
250
    final AlertMessage message = service.createAlertMessage(
251
      Messages.get( titleKey ),
252
      Messages.get( messageKey ),
253
      getPath(),
254
      e.getMessage()
255
    );
256
257
    service.createAlertError( message ).showAndWait();
258
    return false;
259
  }
260
261
  /**
262
   * Returns a best guess at the file encoding. If the encoding could not be
263
   * detected, this will return the default charset for the JVM.
264
   *
265
   * @param bytes The bytes to perform character encoding detection.
266
   *
267
   * @return The character encoding.
268
   */
269
  private Charset detectEncoding( final byte[] bytes ) {
270
    final UniversalDetector detector = new UniversalDetector( null );
271
    detector.handleData( bytes, 0, bytes.length );
272
    detector.dataEnd();
273
274
    final String charset = detector.getDetectedCharset();
275
    final Charset charEncoding = charset == null
276
      ? Charset.defaultCharset()
277
      : Charset.forName( charset.toUpperCase( ENGLISH ) );
278
279
    detector.reset();
280
281
    return charEncoding;
282
  }
283
284
  /**
285
   * Converts the given string to an array of bytes using the encoding that was
286
   * originally detected (if any) and associated with this file.
287
   *
288
   * @param text The text to convert into the original file encoding.
289
   *
290
   * @return A series of bytes ready for writing to a file.
291
   */
292
  private byte[] asBytes( final String text ) {
293
    return text.getBytes( getEncoding() );
294
  }
295
296
  /**
297
   * Converts the given bytes into a Java String. This will call setEncoding
298
   * with the encoding detected by the CharsetDetector.
299
   *
300
   * @param text The text of unknown character encoding.
301
   *
302
   * @return The text, in its auto-detected encoding, as a String.
303
   */
304
  private String asString( final byte[] text ) {
305
    setEncoding( detectEncoding( text ) );
306
    return new String( text, getEncoding() );
307
  }
308
309
  Path getPath() {
310
    return this.path;
311
  }
312
313
  void setPath( final Path path ) {
314
    this.path = path;
315
  }
316
317
  public boolean isModified() {
318
    return this.modified.get();
319
  }
320
321
  ReadOnlyBooleanProperty modifiedProperty() {
322
    return this.modified.getReadOnlyProperty();
323
  }
324
325
  BooleanProperty canUndoProperty() {
326
    return this.canUndo;
327
  }
328
329
  BooleanProperty canRedoProperty() {
330
    return this.canRedo;
331
  }
332
333
  private UndoManager getUndoManager() {
334
    return getEditorPane().getUndoManager();
335
  }
336
337
  /**
338
   * Forwards the request to the editor pane.
339
   *
340
   * @param <T> The type of event listener to add.
341
   * @param <U> The type of consumer to add.
342
   * @param event The event that should trigger updates to the listener.
343
   * @param consumer The listener to receive update events.
344
   */
345
  public <T extends Event, U extends T> void addEventListener(
346
    final EventPattern<? super T, ? extends U> event,
347
    final Consumer<? super U> consumer ) {
348
    getEditorPane().addEventListener( event, consumer );
349
  }
350
351
  /**
352
   * Forwards to the editor pane's listeners for keyboard events.
353
   *
354
   * @param map The new input map to replace the existing keyboard listener.
355
   */
356
  public void addEventListener( final InputMap<InputEvent> map ) {
357
    getEditorPane().addEventListener( map );
358
  }
359
360
  /**
361
   * Forwards to the editor pane's listeners for keyboard events.
362
   *
363
   * @param map The existing input map to remove from the keyboard listeners.
364
   */
365
  public void removeEventListener( final InputMap<InputEvent> map ) {
366
    getEditorPane().removeEventListener( map );
367
  }
368
369
  /**
370
   * Forwards to the editor pane's listeners for text change events.
371
   *
372
   * @param listener The listener to notify when the text changes.
373
   */
374
  public void addTextChangeListener( final ChangeListener<String> listener ) {
375
    getEditorPane().addTextChangeListener( listener );
376
  }
377
  
378
  /**
379
   * Forwards to the editor pane's listeners for paragraph change events.
380
   *
381
   * @param listener The listener to notify when the caret changes paragraphs.
382
   */
383
  public void addCaretParagraphListener( final ChangeListener<Integer> listener){
384
    getEditorPane().addCaretParagraphListener( listener );
385
  }
386
  
387
  /**
388
   * Delegates the request to the editor pane.
389
   *
390
   * @return The text to process.
391
   */
392
  public String getEditorText() {
393
    return getEditorPane().getText();
394
  }
395
396
  /**
397
   * Returns the editor pane, or creates one if it doesn't yet exist.
398
   *
399
   * @return The editor pane, never null.
400
   */
401
  protected EditorPane getEditorPane() {
402
    if( this.editorPane == null ) {
403
      this.editorPane = new MarkdownEditorPane();
404
    }
405
406
    return this.editorPane;
407
  }
408
409
  private AlertService getAlertService() {
410
    return this.alertService;
411
  }
412
413
  private Options getOptions() {
414
    return this.options;
415
  }
416
417
  private Charset getEncoding() {
418
    return this.encoding;
419
  }
420
421
  private void setEncoding( final Charset encoding ) {
422
    this.encoding = encoding;
423
  }
424
}
1425
A src/main/java/com/scrivenvar/FileEditorTabPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import static com.scrivenvar.Messages.get;
31
import com.scrivenvar.predicates.files.FileTypePredicate;
32
import com.scrivenvar.service.Options;
33
import com.scrivenvar.service.Settings;
34
import com.scrivenvar.service.events.AlertMessage;
35
import com.scrivenvar.service.events.AlertService;
36
import static com.scrivenvar.service.events.AlertService.NO;
37
import static com.scrivenvar.service.events.AlertService.YES;
38
import com.scrivenvar.util.Utils;
39
import java.io.File;
40
import java.nio.file.Path;
41
import java.util.ArrayList;
42
import java.util.List;
43
import java.util.function.Consumer;
44
import java.util.prefs.Preferences;
45
import java.util.stream.Collectors;
46
import javafx.beans.property.ReadOnlyBooleanProperty;
47
import javafx.beans.property.ReadOnlyBooleanWrapper;
48
import javafx.beans.property.ReadOnlyObjectProperty;
49
import javafx.beans.property.ReadOnlyObjectWrapper;
50
import javafx.beans.value.ChangeListener;
51
import javafx.beans.value.ObservableValue;
52
import javafx.collections.ListChangeListener;
53
import javafx.collections.ObservableList;
54
import javafx.event.Event;
55
import javafx.scene.Node;
56
import javafx.scene.control.Alert;
57
import javafx.scene.control.ButtonType;
58
import javafx.scene.control.Tab;
59
import javafx.scene.control.TabPane;
60
import javafx.scene.control.TabPane.TabClosingPolicy;
61
import javafx.scene.input.InputEvent;
62
import javafx.stage.FileChooser;
63
import javafx.stage.FileChooser.ExtensionFilter;
64
import javafx.stage.Window;
65
import org.fxmisc.richtext.StyledTextArea;
66
import org.fxmisc.wellbehaved.event.EventPattern;
67
import org.fxmisc.wellbehaved.event.InputMap;
68
69
/**
70
 * Tab pane for file editors.
71
 *
72
 * @author Karl Tauber and White Magic Software, Ltd.
73
 */
74
public final class FileEditorTabPane extends TabPane {
75
  
76
  private final static String FILTER_PREFIX = "Dialog.file.choose.filter";
77
  
78
  private final Options options = Services.load( Options.class );
79
  private final Settings settings = Services.load( Settings.class );
80
  private final AlertService alertService = Services.load( AlertService.class );
81
  
82
  private final ReadOnlyObjectWrapper<FileEditorTab> activeFileEditor = new ReadOnlyObjectWrapper<>();
83
  private final ReadOnlyBooleanWrapper anyFileEditorModified = new ReadOnlyBooleanWrapper();
84
  
85
  public FileEditorTabPane() {
86
    final ObservableList<Tab> tabs = getTabs();
87
    
88
    setFocusTraversable( false );
89
    setTabClosingPolicy( TabClosingPolicy.ALL_TABS );
90
    
91
    addTabChangeListener( (ObservableValue<? extends Tab> tabPane,
92
      final Tab oldTab, final Tab newTab) -> {
93
      if( newTab != null ) {
94
        activeFileEditor.set( (FileEditorTab)newTab.getUserData() );
95
      }
96
    } );
97
    
98
    final ChangeListener<Boolean> modifiedListener = (observable, oldValue, newValue) -> {
99
      for( final Tab tab : tabs ) {
100
        if( ((FileEditorTab)tab.getUserData()).isModified() ) {
101
          this.anyFileEditorModified.set( true );
102
          break;
103
        }
104
      }
105
    };
106
    
107
    tabs.addListener( (ListChangeListener<Tab>)change -> {
108
      while( change.next() ) {
109
        if( change.wasAdded() ) {
110
          change.getAddedSubList().stream().forEach( (tab) -> {
111
            ((FileEditorTab)tab.getUserData()).modifiedProperty().addListener( modifiedListener );
112
          } );
113
        } else if( change.wasRemoved() ) {
114
          change.getRemoved().stream().forEach( (tab) -> {
115
            ((FileEditorTab)tab.getUserData()).modifiedProperty().removeListener( modifiedListener );
116
          } );
117
        }
118
      }
119
120
      // Changes in the tabs may also change anyFileEditorModified property
121
      // (e.g. closed modified file)
122
      modifiedListener.changed( null, null, null );
123
    } );
124
  }
125
  
126
  public <T extends Event, U extends T> void addEventListener(
127
    final EventPattern<? super T, ? extends U> event,
128
    final Consumer<? super U> consumer ) {
129
    getActiveFileEditor().addEventListener( event, consumer );
130
  }
131
132
  /**
133
   * Delegates to the active file editor pane, and, ultimately, to its text
134
   * area.
135
   *
136
   * @param map The map of methods to events.
137
   */
138
  public void addEventListener( final InputMap<InputEvent> map ) {
139
    getActiveFileEditor().addEventListener( map );
140
  }
141
142
  /**
143
   * Remove a keyboard event listener from the active file editor.
144
   *
145
   * @param map The keyboard events to remove.
146
   */
147
  public void removeEventListener( final InputMap<InputEvent> map ) {
148
    getActiveFileEditor().removeEventListener( map );
149
  }
150
151
  /**
152
   * Allows observers to be notified when the current file editor tab changes.
153
   *
154
   * @param listener The listener to notify of tab change events.
155
   */
156
  public void addTabChangeListener( final ChangeListener<Tab> listener ) {
157
    // Observe the tab so that when a new tab is opened or selected,
158
    // a notification is kicked off.
159
    getSelectionModel().selectedItemProperty().addListener( listener );
160
  }
161
  
162
  /**
163
   * Allows clients to manipulate the editor content directly.
164
   *
165
   * @return The text area for the active file editor.
166
   */
167
  public StyledTextArea getEditor() {
168
    return getActiveFileEditor().getEditorPane().getEditor();
169
  }
170
  
171
  public FileEditorTab getActiveFileEditor() {
172
    return this.activeFileEditor.get();
173
  }
174
  
175
  ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() {
176
    return this.activeFileEditor.getReadOnlyProperty();
177
  }
178
  
179
  ReadOnlyBooleanProperty anyFileEditorModifiedProperty() {
180
    return this.anyFileEditorModified.getReadOnlyProperty();
181
  }
182
  
183
  private FileEditorTab createFileEditor( final Path path ) {
184
    final FileEditorTab tab = new FileEditorTab( path );
185
    
186
    tab.setOnCloseRequest( e -> {
187
      if( !canCloseEditor( tab ) ) {
188
        e.consume();
189
      }
190
    } );
191
    
192
    return tab;
193
  }
194
195
  Node getNode() {
196
    return this;
197
  }
198
199
  /**
200
   * Called when the user selects New from the File menu.
201
   *
202
   * @return The newly added tab.
203
   */
204
  FileEditorTab newEditor() {
205
    final FileEditorTab tab = createFileEditor( null );
206
    
207
    getTabs().add( tab );
208
    getSelectionModel().select( tab );
209
    return tab;
210
  }
211
  
212
  List<FileEditorTab> openFileDialog() {
213
    final FileChooser dialog
214
      = createFileChooser( get( "Dialog.file.choose.open.title" ) );
215
    final List<File> files = dialog.showOpenMultipleDialog( getWindow() );
216
    
217
    return (files != null && !files.isEmpty())
218
      ? openFiles( files )
219
      : new ArrayList<>();
220
  }
221
222
  /**
223
   * Opens the files into new editors, unless one of those files was a
224
   * definition file. The definition file is loaded into the definition pane,
225
   * but only the first one selected (multiple definition files will result in a
226
   * warning).
227
   *
228
   * @param files The list of non-definition files that the were requested to
229
   * open.
230
   *
231
   * @return A list of files that can be opened in text editors.
232
   */
233
  private List<FileEditorTab> openFiles( final List<File> files ) {
234
    final List<FileEditorTab> openedEditors = new ArrayList<>();
235
    
236
    final FileTypePredicate predicate
237
      = new FileTypePredicate( createExtensionFilter( "definition" ).getExtensions() );
238
239
    // The user might have opened muliple definitions files. These will
240
    // be discarded from the text editable files.
241
    final List<File> definitions
242
      = files.stream().filter( predicate ).collect( Collectors.toList() );
243
244
    // Create a modifiable list to remove any definition files that were
245
    // opened.
246
    final List<File> editors = new ArrayList<>( files );
247
    editors.removeAll( definitions );
248
249
    // If there are any editor-friendly files opened (e.g,. Markdown, XML), then
250
    // open them up in new tabs.
251
    if( editors.size() > 0 ) {
252
      saveLastDirectory( editors.get( 0 ) );
253
      openedEditors.addAll( openEditors( editors, 0 ) );
254
    }
255
    
256
    if( definitions.size() > 0 ) {
257
      openDefinition( definitions.get( 0 ) );
258
    }
259
    
260
    return openedEditors;
261
  }
262
  
263
  private List<FileEditorTab> openEditors( final List<File> files, final int activeIndex ) {
264
    final int fileTally = files.size();
265
    final List<FileEditorTab> editors = new ArrayList<>( fileTally );
266
    final List<Tab> tabs = getTabs();
267
268
    // Close single unmodified "Untitled" tab.
269
    if( tabs.size() == 1 ) {
270
      final FileEditorTab fileEditor = (FileEditorTab)(tabs.get( 0 ).getUserData());
271
      
272
      if( fileEditor.getPath() == null && !fileEditor.isModified() ) {
273
        closeEditor( fileEditor, false );
274
      }
275
    }
276
    
277
    for( int i = 0; i < fileTally; i++ ) {
278
      final Path path = files.get( i ).toPath();
279
280
      // Check whether file is already opened.
281
      FileEditorTab fileEditor = findEditor( path );
282
      
283
      if( fileEditor == null ) {
284
        fileEditor = createFileEditor( path );
285
        getTabs().add( fileEditor );
286
        editors.add( fileEditor );
287
      }
288
289
      // Select first file.
290
      if( i == activeIndex ) {
291
        getSelectionModel().select( fileEditor );
292
      }
293
    }
294
    
295
    return editors;
296
  }
297
298
  /**
299
   * Called when the user has opened a definition file (using the file open
300
   * dialog box). This will replace the current set of definitions for the
301
   * active tab.
302
   *
303
   * @param definition The file to open.
304
   */
305
  private void openDefinition( final File definition ) {
306
    System.out.println( "open definition file: " + definition.toString() );
307
  }
308
  
309
  boolean saveEditor( final FileEditorTab fileEditor ) {
310
    if( fileEditor == null || !fileEditor.isModified() ) {
311
      return true;
312
    }
313
    
314
    if( fileEditor.getPath() == null ) {
315
      getSelectionModel().select( fileEditor );
316
      
317
      final FileChooser fileChooser = createFileChooser( Messages.get( "Dialog.file.choose.save.title" ) );
318
      final File file = fileChooser.showSaveDialog( getWindow() );
319
      if( file == null ) {
320
        return false;
321
      }
322
      
323
      saveLastDirectory( file );
324
      fileEditor.setPath( file.toPath() );
325
    }
326
    
327
    return fileEditor.save();
328
  }
329
  
330
  boolean saveAllEditors() {
331
    boolean success = true;
332
    
333
    for( FileEditorTab fileEditor : getAllEditors() ) {
334
      if( !saveEditor( fileEditor ) ) {
335
        success = false;
336
      }
337
    }
338
    
339
    return success;
340
  }
341
  
342
  boolean canCloseEditor( final FileEditorTab tab ) {
343
    if( !tab.isModified() ) {
344
      return true;
345
    }
346
    
347
    final AlertMessage message = getAlertService().createAlertMessage(
348
      Messages.get( "Alert.file.close.title" ),
349
      Messages.get( "Alert.file.close.text" ),
350
      tab.getText()
351
    );
352
    
353
    final Alert alert = getAlertService().createAlertConfirmation( message );
354
    final ButtonType response = alert.showAndWait().get();
355
    
356
    return response == YES ? saveEditor( tab ) : response == NO;
357
  }
358
  
359
  private AlertService getAlertService() {
360
    return this.alertService;
361
  }
362
  
363
  boolean closeEditor( FileEditorTab fileEditor, boolean save ) {
364
    if( fileEditor == null ) {
365
      return true;
366
    }
367
    
368
    final Tab tab = fileEditor;
369
    
370
    if( save ) {
371
      Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT );
372
      Event.fireEvent( tab, event );
373
      
374
      if( event.isConsumed() ) {
375
        return false;
376
      }
377
    }
378
    
379
    getTabs().remove( tab );
380
    
381
    if( tab.getOnClosed() != null ) {
382
      Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) );
383
    }
384
    
385
    return true;
386
  }
387
  
388
  boolean closeAllEditors() {
389
    final FileEditorTab[] allEditors = getAllEditors();
390
    final FileEditorTab activeEditor = getActiveFileEditor();
391
392
    // try to save active tab first because in case the user decides to cancel,
393
    // then it stays active
394
    if( activeEditor != null && !canCloseEditor( activeEditor ) ) {
395
      return false;
396
    }
397
398
    // This should be called any time a tab changes.
399
    persistPreferences();
400
401
    // save modified tabs
402
    for( int i = 0; i < allEditors.length; i++ ) {
403
      final FileEditorTab fileEditor = allEditors[ i ];
404
      
405
      if( fileEditor == activeEditor ) {
406
        continue;
407
      }
408
      
409
      if( fileEditor.isModified() ) {
410
        // activate the modified tab to make its modified content visible to the user
411
        getSelectionModel().select( i );
412
        
413
        if( !canCloseEditor( fileEditor ) ) {
414
          return false;
415
        }
416
      }
417
    }
418
419
    // Close all tabs.
420
    for( final FileEditorTab fileEditor : allEditors ) {
421
      if( !closeEditor( fileEditor, false ) ) {
422
        return false;
423
      }
424
    }
425
    
426
    return getTabs().isEmpty();
427
  }
428
  
429
  private FileEditorTab[] getAllEditors() {
430
    final ObservableList<Tab> tabs = getTabs();
431
    final int length = tabs.size();
432
    final FileEditorTab[] allEditors = new FileEditorTab[ length ];
433
    
434
    for( int i = 0; i < length; i++ ) {
435
      allEditors[ i ] = (FileEditorTab)tabs.get( i ).getUserData();
436
    }
437
    
438
    return allEditors;
439
  }
440
441
  /**
442
   * Returns the file editor tab that has the given path.
443
   *
444
   * @return null No file editor tab for the given path was found.
445
   */
446
  private FileEditorTab findEditor( final Path path ) {
447
    for( final Tab tab : getTabs() ) {
448
      final FileEditorTab fileEditor = (FileEditorTab)tab;
449
      
450
      if( fileEditor.isPath( path ) ) {
451
        return fileEditor;
452
      }
453
    }
454
    
455
    return null;
456
  }
457
  
458
  private FileChooser createFileChooser( String title ) {
459
    final FileChooser fileChooser = new FileChooser();
460
    
461
    fileChooser.setTitle( title );
462
    fileChooser.getExtensionFilters().addAll(
463
      createExtensionFilters() );
464
    
465
    final String lastDirectory = getState().get( "lastDirectory", null );
466
    File file = new File( (lastDirectory != null) ? lastDirectory : "." );
467
    
468
    if( !file.isDirectory() ) {
469
      file = new File( "." );
470
    }
471
    
472
    fileChooser.setInitialDirectory( file );
473
    return fileChooser;
474
  }
475
  
476
  private List<ExtensionFilter> createExtensionFilters() {
477
    final List<ExtensionFilter> list = new ArrayList<>();
478
479
    // TODO: Return a list of all properties that match the filter prefix.
480
    // This will allow dynamic filters to be added and removed just by
481
    // updating the properties file.
482
    list.add( createExtensionFilter( "markdown" ) );
483
    list.add( createExtensionFilter( "definition" ) );
484
    list.add( createExtensionFilter( "xml" ) );
485
    list.add( createExtensionFilter( "all" ) );
486
    return list;
487
  }
488
  
489
  private ExtensionFilter createExtensionFilter( final String filetype ) {
490
    final String tKey = String.format( "%s.title.%s", FILTER_PREFIX, filetype );
491
    final String eKey = String.format( "%s.ext.%s", FILTER_PREFIX, filetype );
492
    
493
    return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) );
494
  }
495
  
496
  private List<String> getExtensions( final String key ) {
497
    return getStringSettingList( key );
498
  }
499
  
500
  private List<String> getStringSettingList( String key ) {
501
    return getStringSettingList( key, null );
502
  }
503
  
504
  private List<String> getStringSettingList( String key, List<String> values ) {
505
    return getSettings().getStringSettingList( key, values );
506
  }
507
  
508
  private void saveLastDirectory( final File file ) {
509
    getState().put( "lastDirectory", file.getParent() );
510
  }
511
  
512
  public void restorePreferences() {
513
    int activeIndex = 0;
514
    
515
    final Preferences preferences = getState();
516
    final String[] fileNames = Utils.getPrefsStrings( preferences, "file" );
517
    final String activeFileName = preferences.get( "activeFile", null );
518
    
519
    final ArrayList<File> files = new ArrayList<>( fileNames.length );
520
    
521
    for( final String fileName : fileNames ) {
522
      final File file = new File( fileName );
523
      
524
      if( file.exists() ) {
525
        files.add( file );
526
        
527
        if( fileName.equals( activeFileName ) ) {
528
          activeIndex = files.size() - 1;
529
        }
530
      }
531
    }
532
    
533
    if( files.isEmpty() ) {
534
      newEditor();
535
      return;
536
    }
537
    
538
    openEditors( files, activeIndex );
539
  }
540
  
541
  public void persistPreferences() {
542
    final ObservableList<Tab> allEditors = getTabs();
543
    final List<String> fileNames = new ArrayList<>( allEditors.size() );
544
    
545
    for( final Tab tab : allEditors ) {
546
      final FileEditorTab fileEditor = (FileEditorTab)tab;
547
      
548
      if( fileEditor.getPath() != null ) {
549
        fileNames.add( fileEditor.getPath().toString() );
550
      }
551
    }
552
    
553
    final Preferences preferences = getState();
554
    Utils.putPrefsStrings( preferences, "file", fileNames.toArray( new String[ fileNames.size() ] ) );
555
    
556
    final FileEditorTab activeEditor = getActiveFileEditor();
557
    
558
    if( activeEditor != null && activeEditor.getPath() != null ) {
559
      preferences.put( "activeFile", activeEditor.getPath().toString() );
560
    } else {
561
      preferences.remove( "activeFile" );
562
    }
563
  }
564
  
565
  private Settings getSettings() {
566
    return this.settings;
567
  }
568
  
569
  protected Options getOptions() {
570
    return this.options;
571
  }
572
  
573
  private Window getWindow() {
574
    return getScene().getWindow();
575
  }
576
  
577
  protected Preferences getState() {
578
    return getOptions().getState();
579
  }
580
}
1581
A src/main/java/com/scrivenvar/Main.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import static com.scrivenvar.Constants.LOGO_128;
31
import static com.scrivenvar.Constants.LOGO_16;
32
import static com.scrivenvar.Constants.LOGO_256;
33
import static com.scrivenvar.Constants.LOGO_32;
34
import static com.scrivenvar.Constants.LOGO_512;
35
import com.scrivenvar.service.Options;
36
import com.scrivenvar.service.events.AlertService;
37
import com.scrivenvar.util.StageState;
38
import javafx.application.Application;
39
import javafx.scene.Scene;
40
import javafx.scene.image.Image;
41
import javafx.stage.Stage;
42
43
/**
44
 * Main application entry point. The application allows users to edit Markdown
45
 * files and see a real-time preview of the edits.
46
 *
47
 * @author Karl Tauber and White Magic Software, Ltd.
48
 */
49
public final class Main extends Application {
50
51
  private static Application app;
52
53
  private final MainWindow mainWindow = new MainWindow();
54
  private final Options options = Services.load( Options.class );
55
56
  public static void main( String[] args ) {
57
    launch( args );
58
  }
59
60
  /**
61
   * Application entry point.
62
   *
63
   * @param stage The primary application stage.
64
   *
65
   * @throws Exception Could not read configuration file.
66
   */
67
  @Override
68
  public void start( final Stage stage ) throws Exception {
69
    initApplication();
70
    initState( stage );
71
    initStage( stage );
72
    initAlertService();
73
    
74
    stage.show();
75
  }
76
77
  private void initApplication() {
78
    app = this;
79
  }
80
81
  private Options getOptions() {
82
    return this.options;
83
  }
84
85
  private String getApplicationTitle() {
86
    return Messages.get( "Main.title" );
87
  }
88
89
  private StageState initState( Stage stage ) {
90
    return new StageState( stage, getOptions().getState() );
91
  }
92
93
  private void initStage( Stage stage ) {
94
    stage.getIcons().addAll(
95
      new Image( LOGO_16 ),
96
      new Image( LOGO_32 ),
97
      new Image( LOGO_128 ),
98
      new Image( LOGO_256 ),
99
      new Image( LOGO_512 ) );
100
    stage.setTitle( getApplicationTitle() );
101
    stage.setScene( getScene() );
102
  }
103
104
  private void initAlertService() {
105
    final AlertService service = Services.load( AlertService.class );
106
    service.setWindow( getScene().getWindow() );
107
  }
108
109
  private Scene getScene() {
110
    return getMainWindow().getScene();
111
  }
112
113
  private MainWindow getMainWindow() {
114
    return this.mainWindow;
115
  }
116
117
  private static Application getApplication() {
118
    return app;
119
  }
120
121
  public static void showDocument( String uri ) {
122
    getApplication().getHostServices().showDocument( uri );
123
  }
124
}
1125
A src/main/java/com/scrivenvar/MainWindow.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import static com.scrivenvar.Constants.LOGO_32;
31
import static com.scrivenvar.Messages.get;
32
import com.scrivenvar.definition.DefinitionPane;
33
import com.scrivenvar.editor.MarkdownEditorPane;
34
import com.scrivenvar.editor.VariableNameInjector;
35
import com.scrivenvar.preview.HTMLPreviewPane;
36
import com.scrivenvar.processors.HTMLPreviewProcessor;
37
import com.scrivenvar.processors.MarkdownCaretInsertionProcessor;
38
import com.scrivenvar.processors.MarkdownCaretReplacementProcessor;
39
import com.scrivenvar.processors.MarkdownProcessor;
40
import com.scrivenvar.processors.Processor;
41
import com.scrivenvar.processors.VariableProcessor;
42
import com.scrivenvar.service.Options;
43
import com.scrivenvar.util.Action;
44
import com.scrivenvar.util.ActionUtils;
45
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_DEFINITION;
46
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_EDITOR;
47
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_PREVIEW;
48
import com.scrivenvar.yaml.YamlParser;
49
import com.scrivenvar.yaml.YamlTreeAdapter;
50
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD;
51
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE;
52
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT;
53
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT;
54
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT;
55
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT;
56
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER;
57
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC;
58
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK;
59
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL;
60
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL;
61
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT;
62
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT;
63
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT;
64
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH;
65
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO;
66
import java.io.IOException;
67
import java.io.InputStream;
68
import java.util.Map;
69
import java.util.function.Function;
70
import java.util.prefs.Preferences;
71
import javafx.beans.binding.Bindings;
72
import javafx.beans.binding.BooleanBinding;
73
import javafx.beans.property.BooleanProperty;
74
import javafx.beans.property.SimpleBooleanProperty;
75
import javafx.beans.value.ObservableBooleanValue;
76
import javafx.beans.value.ObservableValue;
77
import javafx.collections.ListChangeListener.Change;
78
import javafx.collections.ObservableList;
79
import javafx.event.Event;
80
import javafx.scene.Node;
81
import javafx.scene.Scene;
82
import javafx.scene.control.Alert;
83
import javafx.scene.control.Alert.AlertType;
84
import javafx.scene.control.Menu;
85
import javafx.scene.control.MenuBar;
86
import javafx.scene.control.SplitPane;
87
import javafx.scene.control.Tab;
88
import javafx.scene.control.ToolBar;
89
import javafx.scene.control.TreeView;
90
import javafx.scene.image.Image;
91
import javafx.scene.image.ImageView;
92
import static javafx.scene.input.KeyCode.ESCAPE;
93
import javafx.scene.input.KeyEvent;
94
import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED;
95
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
96
import javafx.scene.layout.BorderPane;
97
import javafx.scene.layout.VBox;
98
import javafx.stage.Window;
99
import javafx.stage.WindowEvent;
100
101
/**
102
 * Main window containing a tab pane in the center for file editors.
103
 *
104
 * @author Karl Tauber and White Magic Software, Ltd.
105
 */
106
public class MainWindow {
107
108
  private final Options options = Services.load( Options.class );
109
110
  private Scene scene;
111
112
  private TreeView<String> treeView;
113
  private DefinitionPane definitionPane;
114
  private FileEditorTabPane fileEditorPane;
115
  private HTMLPreviewPane previewPane;
116
117
  private VariableNameInjector variableNameInjector;
118
119
  private YamlTreeAdapter yamlTreeAdapter;
120
  private YamlParser yamlParser;
121
122
  private MenuBar menuBar;
123
124
  public MainWindow() {
125
    initLayout();
126
    initTabAddedListener();
127
    restorePreferences();
128
    initTabChangeListener();
129
    initVariableNameInjector();
130
  }
131
132
  private void initLayout() {
133
    final SplitPane splitPane = new SplitPane(
134
      getDefinitionPane().getNode(),
135
      getFileEditorPane().getNode(),
136
      getPreviewPane().getNode() );
137
138
    splitPane.setDividerPositions(
139
      getFloat( K_PANE_SPLIT_DEFINITION, .10f ),
140
      getFloat( K_PANE_SPLIT_EDITOR, .45f ),
141
      getFloat( K_PANE_SPLIT_PREVIEW, .45f ) );
142
143
    // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restorePreferences-splitpane.html
144
    final BorderPane borderPane = new BorderPane();
145
    borderPane.setPrefSize( 1024, 800 );
146
    borderPane.setTop( createMenuBar() );
147
    borderPane.setCenter( splitPane );
148
149
    final Scene appScene = new Scene( borderPane );
150
    setScene( appScene );
151
    appScene.getStylesheets().add( Constants.STYLESHEET_PREVIEW );
152
    appScene.windowProperty().addListener(
153
      (observable, oldWindow, newWindow) -> {
154
        newWindow.setOnCloseRequest( e -> {
155
          if( !getFileEditorPane().closeAllEditors() ) {
156
            e.consume();
157
          }
158
        } );
159
160
        // Workaround JavaFX bug: deselect menubar if window loses focus.
161
        newWindow.focusedProperty().addListener(
162
          (obs, oldFocused, newFocused) -> {
163
            if( !newFocused ) {
164
              // Send an ESC key event to the menubar
165
              this.menuBar.fireEvent(
166
                new KeyEvent(
167
                  KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE,
168
                  false, false, false, false ) );
169
            }
170
          } );
171
      } );
172
  }
173
174
  private void initVariableNameInjector() {
175
    setVariableNameInjector( new VariableNameInjector(
176
      getFileEditorPane(),
177
      getDefinitionPane() )
178
    );
179
  }
180
181
  private Window getWindow() {
182
    return getScene().getWindow();
183
  }
184
185
  public Scene getScene() {
186
    return this.scene;
187
  }
188
189
  private void setScene( Scene scene ) {
190
    this.scene = scene;
191
  }
192
193
  /**
194
   * Creates a boolean property that is bound to another boolean value of the
195
   * active editor.
196
   */
197
  private BooleanProperty createActiveBooleanProperty(
198
    final Function<FileEditorTab, ObservableBooleanValue> func ) {
199
200
    final BooleanProperty b = new SimpleBooleanProperty();
201
    final FileEditorTab tab = getActiveFileEditor();
202
203
    if( tab != null ) {
204
      b.bind( func.apply( tab ) );
205
    }
206
207
    getFileEditorPane().activeFileEditorProperty().addListener(
208
      (observable, oldFileEditor, newFileEditor) -> {
209
        b.unbind();
210
211
        if( newFileEditor != null ) {
212
          b.bind( func.apply( newFileEditor ) );
213
        } else {
214
          b.set( false );
215
        }
216
      } );
217
218
    return b;
219
  }
220
221
  //---- File actions -------------------------------------------------------
222
  private void fileNew() {
223
    getFileEditorPane().newEditor();
224
  }
225
226
  private void fileOpen() {
227
    getFileEditorPane().openFileDialog();
228
  }
229
230
  private void fileClose() {
231
    getFileEditorPane().closeEditor( getActiveFileEditor(), true );
232
  }
233
234
  private void fileCloseAll() {
235
    getFileEditorPane().closeAllEditors();
236
  }
237
238
  private void fileSave() {
239
    getFileEditorPane().saveEditor( getActiveFileEditor() );
240
  }
241
242
  private void fileSaveAll() {
243
    getFileEditorPane().saveAllEditors();
244
  }
245
246
  private void fileExit() {
247
    final Window window = getWindow();
248
    Event.fireEvent( window,
249
      new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) );
250
  }
251
252
  //---- Help actions -------------------------------------------------------
253
  private void helpAbout() {
254
    Alert alert = new Alert( AlertType.INFORMATION );
255
    alert.setTitle( Messages.get( "Dialog.about.title" ) );
256
    alert.setHeaderText( Messages.get( "Dialog.about.header" ) );
257
    alert.setContentText( Messages.get( "Dialog.about.content" ) );
258
    alert.setGraphic( new ImageView( new Image( LOGO_32 ) ) );
259
    alert.initOwner( getWindow() );
260
261
    alert.showAndWait();
262
  }
263
264
  private FileEditorTabPane getFileEditorPane() {
265
    if( this.fileEditorPane == null ) {
266
      this.fileEditorPane = createFileEditorPane();
267
    }
268
269
    return this.fileEditorPane;
270
  }
271
272
  /**
273
   * Create an editor pane to hold file editor tabs.
274
   *
275
   * @return A new instance, never null.
276
   */
277
  private FileEditorTabPane createFileEditorPane() {
278
    return new FileEditorTabPane();
279
  }
280
281
  /**
282
   * Reloads the preferences from the previous load.
283
   */
284
  private void restorePreferences() {
285
    getFileEditorPane().restorePreferences();
286
  }
287
288
  private void initTabAddedListener() {
289
    final FileEditorTabPane editorPane = getFileEditorPane();
290
291
    // Make sure the text processor kicks off when new files are opened.
292
    final ObservableList<Tab> tabs = editorPane.getTabs();
293
294
    // Update the preview pane on tab changes.
295
    tabs.addListener( (final Change<? extends Tab> change) -> {
296
      while( change.next() ) {
297
        if( change.wasAdded() ) {
298
          // Multiple tabs can be added simultaneously.
299
          for( final Tab newTab : change.getAddedSubList() ) {
300
            final FileEditorTab tab = (FileEditorTab)newTab;
301
302
            initTextChangeListener( tab );
303
            initCaretParagraphListener( tab );
304
            process( tab );
305
          }
306
        }
307
      }
308
    } );
309
  }
310
311
  /**
312
   * Listen for tab changes.
313
   */
314
  private void initTabChangeListener() {
315
    final FileEditorTabPane editorPane = getFileEditorPane();
316
317
    // Update the preview pane changing tabs.
318
    editorPane.addTabChangeListener(
319
      (ObservableValue<? extends Tab> tabPane,
320
        final Tab oldTab, final Tab newTab) -> {
321
322
        final FileEditorTab tab = (FileEditorTab)newTab;
323
324
        if( tab != null ) {
325
          // When a new tab is selected, ensure that the base path to images
326
          // is set correctly.
327
          getPreviewPane().setPath( tab.getPath() );
328
          process( tab );
329
        }
330
      } );
331
  }
332
333
  private void initTextChangeListener( final FileEditorTab tab ) {
334
    tab.addTextChangeListener( (ObservableValue<? extends String> editor,
335
      final String oldValue, final String newValue) -> {
336
      process( tab );
337
    } );
338
  }
339
340
  private void initCaretParagraphListener( final FileEditorTab tab ) {
341
    tab.addCaretParagraphListener( (ObservableValue<? extends Integer> editor,
342
      final Integer oldValue, final Integer newValue) -> {
343
      process( tab );
344
    } );
345
  }
346
347
  /**
348
   * Called whenever the preview pane becomes out of sync with the file editor
349
   * tab. This can be called when the text changes, the caret paragraph changes,
350
   * or the file tab changes.
351
   *
352
   * @param tab The file editor tab that has been changed in some fashion.
353
   */
354
  private void process( final FileEditorTab tab ) {
355
    // TODO: Use a factory based on the filename extension. The default
356
    // extension will be for a markdown file (e.g., on file new).
357
    final Processor<String> hpp = new HTMLPreviewProcessor( getPreviewPane() );
358
    final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp );
359
    final Processor<String> mp = new MarkdownProcessor( mcrp );
360
    final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, tab.getCaretPosition() );
361
    final Processor<String> vp = new VariableProcessor( mcip, getResolvedMap() );
362
    
363
    vp.processChain( tab.getEditorText() );
364
  }
365
366
  private MarkdownEditorPane getActiveEditor() {
367
    return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane());
368
  }
369
370
  private FileEditorTab getActiveFileEditor() {
371
    return getFileEditorPane().getActiveFileEditor();
372
  }
373
374
  protected DefinitionPane createDefinitionPane() {
375
    return new DefinitionPane( getTreeView() );
376
  }
377
378
  private DefinitionPane getDefinitionPane() {
379
    if( this.definitionPane == null ) {
380
      this.definitionPane = createDefinitionPane();
381
    }
382
383
    return this.definitionPane;
384
  }
385
386
  public MenuBar getMenuBar() {
387
    return this.menuBar;
388
  }
389
390
  public void setMenuBar( MenuBar menuBar ) {
391
    this.menuBar = menuBar;
392
  }
393
394
  public VariableNameInjector getVariableNameInjector() {
395
    return this.variableNameInjector;
396
  }
397
398
  public void setVariableNameInjector( VariableNameInjector variableNameInjector ) {
399
    this.variableNameInjector = variableNameInjector;
400
  }
401
402
  private float getFloat( final String key, final float defaultValue ) {
403
    return getPreferences().getFloat( key, defaultValue );
404
  }
405
406
  private Preferences getPreferences() {
407
    return getOptions().getState();
408
  }
409
410
  private Options getOptions() {
411
    return this.options;
412
  }
413
414
  private synchronized TreeView<String> getTreeView() throws RuntimeException {
415
    if( this.treeView == null ) {
416
      try {
417
        this.treeView = createTreeView();
418
      } catch( IOException ex ) {
419
420
        // TODO: Pop an error message.
421
        throw new RuntimeException( ex );
422
      }
423
    }
424
425
    return this.treeView;
426
  }
427
428
  private InputStream asStream( final String resource ) {
429
    return getClass().getResourceAsStream( resource );
430
  }
431
432
  private TreeView<String> createTreeView() throws IOException {
433
    // TODO: Associate variable file with path to current file.
434
    return getYamlTreeAdapter().adapt(
435
      asStream( "/com/scrivenvar/variables.yaml" ),
436
      get( "Pane.defintion.node.root.title" )
437
    );
438
  }
439
440
  private Map<String, String> getResolvedMap() {
441
    return getYamlParser().createResolvedMap();
442
  }
443
444
  private YamlTreeAdapter getYamlTreeAdapter() {
445
    if( this.yamlTreeAdapter == null ) {
446
      setYamlTreeAdapter( new YamlTreeAdapter( getYamlParser() ) );
447
    }
448
449
    return this.yamlTreeAdapter;
450
  }
451
452
  private void setYamlTreeAdapter( final YamlTreeAdapter yamlTreeAdapter ) {
453
    this.yamlTreeAdapter = yamlTreeAdapter;
454
  }
455
456
  private YamlParser getYamlParser() {
457
    if( this.yamlParser == null ) {
458
      setYamlParser( new YamlParser() );
459
    }
460
461
    return this.yamlParser;
462
  }
463
464
  private void setYamlParser( final YamlParser yamlParser ) {
465
    this.yamlParser = yamlParser;
466
  }
467
468
  private Node createMenuBar() {
469
    final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
470
471
    // File actions
472
    Action fileNewAction = new Action( Messages.get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
473
    Action fileOpenAction = new Action( Messages.get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
474
    Action fileCloseAction = new Action( Messages.get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
475
    Action fileCloseAllAction = new Action( Messages.get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
476
    Action fileSaveAction = new Action( Messages.get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
477
      createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
478
    Action fileSaveAllAction = new Action( Messages.get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
479
      Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
480
    Action fileExitAction = new Action( Messages.get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
481
482
    // Edit actions
483
    Action editUndoAction = new Action( Messages.get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
484
      e -> getActiveEditor().undo(),
485
      createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
486
    Action editRedoAction = new Action( Messages.get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
487
      e -> getActiveEditor().redo(),
488
      createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
489
490
    // Insert actions
491
    Action insertBoldAction = new Action( Messages.get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
492
      e -> getActiveEditor().surroundSelection( "**", "**" ),
493
      activeFileEditorIsNull );
494
    Action insertItalicAction = new Action( Messages.get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
495
      e -> getActiveEditor().surroundSelection( "*", "*" ),
496
      activeFileEditorIsNull );
497
    Action insertStrikethroughAction = new Action( Messages.get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
498
      e -> getActiveEditor().surroundSelection( "~~", "~~" ),
499
      activeFileEditorIsNull );
500
    Action insertBlockquoteAction = new Action( Messages.get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
501
      e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
502
      activeFileEditorIsNull );
503
    Action insertCodeAction = new Action( Messages.get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
504
      e -> getActiveEditor().surroundSelection( "`", "`" ),
505
      activeFileEditorIsNull );
506
    Action insertFencedCodeBlockAction = new Action( Messages.get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
507
      e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", Messages.get( "Main.menu.insert.fenced_code_block.prompt" ) ),
508
      activeFileEditorIsNull );
509
510
    Action insertLinkAction = new Action( Messages.get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
511
      e -> getActiveEditor().insertLink(),
512
      activeFileEditorIsNull );
513
    Action insertImageAction = new Action( Messages.get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
514
      e -> getActiveEditor().insertImage(),
515
      activeFileEditorIsNull );
516
517
    final Action[] headers = new Action[ 6 ];
518
519
    // Insert header actions (H1 ... H6)
520
    for( int i = 1; i <= 6; i++ ) {
521
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
522
      final String markup = String.format( "\n\n%s ", hashes );
523
      final String text = Messages.get( "Main.menu.insert.header_" + i );
524
      final String accelerator = "Shortcut+" + i;
525
      final String prompt = Messages.get( "Main.menu.insert.header_" + i + ".prompt" );
526
527
      headers[ i - 1 ] = new Action( text, accelerator, HEADER,
528
        e -> getActiveEditor().surroundSelection( markup, "", prompt ),
529
        activeFileEditorIsNull );
530
    }
531
532
    Action insertUnorderedListAction = new Action( Messages.get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
533
      e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
534
      activeFileEditorIsNull );
535
    Action insertOrderedListAction = new Action( Messages.get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
536
      e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
537
      activeFileEditorIsNull );
538
    Action insertHorizontalRuleAction = new Action( Messages.get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
539
      e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
540
      activeFileEditorIsNull );
541
542
    // Help actions
543
    Action helpAboutAction = new Action( Messages.get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
544
545
    //---- MenuBar ----
546
    Menu fileMenu = ActionUtils.createMenu( Messages.get( "Main.menu.file" ),
547
      fileNewAction,
548
      fileOpenAction,
549
      null,
550
      fileCloseAction,
551
      fileCloseAllAction,
552
      null,
553
      fileSaveAction,
554
      fileSaveAllAction,
555
      null,
556
      fileExitAction );
557
558
    Menu editMenu = ActionUtils.createMenu( Messages.get( "Main.menu.edit" ),
559
      editUndoAction,
560
      editRedoAction );
561
562
    Menu insertMenu = ActionUtils.createMenu( Messages.get( "Main.menu.insert" ),
563
      insertBoldAction,
564
      insertItalicAction,
565
      insertStrikethroughAction,
566
      insertBlockquoteAction,
567
      insertCodeAction,
568
      insertFencedCodeBlockAction,
569
      null,
570
      insertLinkAction,
571
      insertImageAction,
572
      null,
573
      headers[ 0 ],
574
      headers[ 1 ],
575
      headers[ 2 ],
576
      headers[ 3 ],
577
      headers[ 4 ],
578
      headers[ 5 ],
579
      null,
580
      insertUnorderedListAction,
581
      insertOrderedListAction,
582
      insertHorizontalRuleAction );
583
584
    Menu helpMenu = ActionUtils.createMenu( Messages.get( "Main.menu.help" ),
585
      helpAboutAction );
586
587
    menuBar = new MenuBar( fileMenu, editMenu, insertMenu, helpMenu );
588
589
    //---- ToolBar ----
590
    ToolBar toolBar = ActionUtils.createToolBar(
591
      fileNewAction,
592
      fileOpenAction,
593
      fileSaveAction,
594
      null,
595
      editUndoAction,
596
      editRedoAction,
597
      null,
598
      insertBoldAction,
599
      insertItalicAction,
600
      insertBlockquoteAction,
601
      insertCodeAction,
602
      insertFencedCodeBlockAction,
603
      null,
604
      insertLinkAction,
605
      insertImageAction,
606
      null,
607
      headers[ 0 ],
608
      null,
609
      insertUnorderedListAction,
610
      insertOrderedListAction );
611
612
    return new VBox( menuBar, toolBar );
613
  }
614
615
  private synchronized HTMLPreviewPane getPreviewPane() {
616
    if( this.previewPane == null ) {
617
      this.previewPane = new HTMLPreviewPane();
618
    }
619
620
    return this.previewPane;
621
  }
622
623
}
1624
A src/main/java/com/scrivenvar/Messages.java
1
/*
2
 * Copyright (c) 2016 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  * Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  * Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar;
28
29
import static com.scrivenvar.Constants.BUNDLE_NAME;
30
import java.text.MessageFormat;
31
import java.util.ResourceBundle;
32
import java.util.Stack;
33
34
/**
35
 * Recursively resolves message properties. Property values can refer to other
36
 * properties using a <code>${var}</code> syntax.
37
 *
38
 * @author Karl Tauber, Dave Jarvis
39
 */
40
public class Messages {
41
42
  private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle( BUNDLE_NAME );
43
44
  private Messages() {
45
  }
46
47
  /**
48
   * Return the value of a resource bundle value after having resolved any
49
   * references to other bundle variables.
50
   *
51
   * @param props The bundle containing resolvable properties.
52
   * @param s The value for a key to resolve.
53
   *
54
   * @return The value of the key with all references recursively dereferenced.
55
   */
56
  private static String resolve( ResourceBundle props, String s ) {
57
    final int len = s.length();
58
    final Stack<StringBuilder> stack = new Stack<>();
59
60
    StringBuilder sb = new StringBuilder( 256 );
61
    boolean open = false;
62
63
    for( int i = 0; i < len; i++ ) {
64
      final char c = s.charAt( i );
65
66
      switch( c ) {
67
        case '$': {
68
          if( i + 1 < len && s.charAt( i + 1 ) == '{' ) {
69
            stack.push( sb );
70
            sb = new StringBuilder( 256 );
71
            i++;
72
            open = true;
73
          }
74
75
          break;
76
        }
77
78
        case '}': {
79
          if( open ) {
80
            open = false;
81
            final String name = sb.toString();
82
83
            sb = stack.pop();
84
            sb.append( props.getString( name ) );
85
            break;
86
          }
87
        }
88
89
        default: {
90
          sb.append( c );
91
          break;
92
        }
93
      }
94
    }
95
96
    if( open ) {
97
      throw new IllegalArgumentException( "missing '}'" );
98
    }
99
100
    return sb.toString();
101
  }
102
103
  /**
104
   * Returns the value for a key from the message bundle.
105
   *
106
   * @param key Retrieve the value for this key.
107
   *
108
   * @return The value for the key.
109
   */
110
  public static String get( String key ) {
111
    String result;
112
113
    try {
114
      result = resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) );
115
    } catch( Exception e ) {
116
      
117
      // Instead of crashing, launch the application and show the resource
118
      // name.
119
      result = key;
120
    }
121
122
    return result;
123
  }
124
125
  /**
126
   * Returns the value for a key from the message bundle with the arguments
127
   * replacing <code>{#}</code> place holders.
128
   *
129
   * @param key Retrieve the value for this key.
130
   * @param args The values to substitute for place holders.
131
   *
132
   * @return The value for the key.
133
   */
134
  public static String get( String key, Object... args ) {
135
    return MessageFormat.format( get( key ), args );
136
  }
137
}
1138
A src/main/java/com/scrivenvar/Services.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import java.util.ServiceLoader;
31
32
/**
33
 * Responsible for loading services.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public class Services {
38
39
  /**
40
   * Loads a service based on its interface definition.
41
   *
42
   * @param <T> The service to load.
43
   * @param api The interface definition for the service.
44
   *
45
   * @return A class that implements the interface.
46
   */
47
  public static <T> T load( Class<T> api ) {
48
    final ServiceLoader<T> services = ServiceLoader.load( api );
49
    T result = null;
50
51
    for( T service : services ) {
52
      result = service;
53
54
      if( result != null ) {
55
        break;
56
      }
57
    }
58
59
    if( result == null ) {
60
      throw new RuntimeException( "No implementation for: " + api );
61
    }
62
63
    return result;
64
  }
65
}
166
A src/main/java/com/scrivenvar/TestDefinitionPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import com.scrivenvar.definition.DefinitionPane;
31
import static javafx.application.Application.launch;
32
import javafx.scene.control.TreeItem;
33
import javafx.scene.control.TreeView;
34
import javafx.stage.Stage;
35
36
/**
37
 * TestDefinitionPane application for debugging.
38
 */
39
public final class TestDefinitionPane extends TestHarness {
40
  /**
41
   * Application entry point.
42
   *
43
   * @param stage The primary application stage.
44
   *
45
   * @throws Exception Could not read configuration file.
46
   */
47
  @Override
48
  public void start( final Stage stage ) throws Exception {
49
    super.start( stage );
50
51
    TreeView<String> root = createTreeView();
52
    DefinitionPane pane = createDefinitionPane( root );
53
54
    test( pane, "language.ai.", "article" );
55
    test( pane, "language.ai", "ai" );
56
    test( pane, "l", "location" );
57
    test( pane, "la", "language" );
58
    test( pane, "c.p.n", "name" );
59
    test( pane, "c.p.n.", "First" );
60
    test( pane, "...", "c" );
61
    test( pane, "foo", "c" );
62
    test( pane, "foo.bar", "c" );
63
    test( pane, "", "c" );
64
    test( pane, "c", "protagonist" );
65
    test( pane, "c.", "protagonist" );
66
    test( pane, "c.p", "protagonist" );
67
    test( pane, "c.protagonist", "protagonist" );
68
69
    System.exit( 0 );
70
  }
71
72
  private void test( DefinitionPane pane, String path, String value ) {
73
    System.out.println( "---------------------------" );
74
    System.out.println( "Find Path: '" + path + "'" );
75
    final TreeItem<String> node = pane.findNode( path );
76
    System.out.println( "Path Node: " + node );
77
    System.out.println( "Node Val : " + node.getValue() );
78
  }
79
80
  public static void main( String[] args ) {
81
    launch( args );
82
  }
83
}
184
A src/main/java/com/scrivenvar/TestHarness.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import static com.scrivenvar.Messages.get;
31
import com.scrivenvar.definition.DefinitionPane;
32
import com.scrivenvar.yaml.YamlParser;
33
import com.scrivenvar.yaml.YamlTreeAdapter;
34
import java.io.IOException;
35
import java.io.InputStream;
36
import javafx.application.Application;
37
import javafx.scene.Scene;
38
import javafx.scene.control.TreeView;
39
import javafx.scene.layout.BorderPane;
40
import javafx.stage.Stage;
41
import org.fxmisc.flowless.VirtualizedScrollPane;
42
import org.fxmisc.richtext.StyleClassedTextArea;
43
44
/**
45
 * TestDefinitionPane application for debugging and head-banging.
46
 */
47
public abstract class TestHarness extends Application {
48
49
  private static Application app;
50
  private Scene scene;
51
52
  /**
53
   * Application entry point.
54
   *
55
   * @param stage The primary application stage.
56
   *
57
   * @throws Exception Could not read configuration file.
58
   */
59
  @Override
60
  public void start( final Stage stage ) throws Exception {
61
    initApplication();
62
    initScene();
63
    initStage( stage );
64
  }
65
  
66
  protected TreeView<String> createTreeView() throws IOException {
67
    return new YamlTreeAdapter( new YamlParser() ).adapt(
68
      asStream( "/com/scrivenvar/variables.yaml" ),
69
      get( "Pane.defintion.node.root.title" )
70
    );
71
  }
72
  
73
  protected DefinitionPane createDefinitionPane( TreeView<String> root ) {
74
    return new DefinitionPane( root );
75
  }
76
77
  private void initApplication() {
78
    app = this;
79
  }
80
81
  private void initScene() {
82
    final StyleClassedTextArea editor = new StyleClassedTextArea( false );
83
    final VirtualizedScrollPane<StyleClassedTextArea> scrollPane = new VirtualizedScrollPane<>( editor );
84
85
    final BorderPane borderPane = new BorderPane();
86
    borderPane.setPrefSize( 1024, 800 );
87
    borderPane.setCenter( scrollPane );
88
89
    setScene( new Scene( borderPane ) );
90
  }
91
92
  private void initStage( Stage stage ) {
93
    stage.setScene( getScene() );
94
  }
95
96
  private Scene getScene() {
97
    return this.scene;
98
  }
99
100
  private void setScene( Scene scene ) {
101
    this.scene = scene;
102
  }
103
104
  private static Application getApplication() {
105
    return app;
106
  }
107
108
  public static void showDocument( String uri ) {
109
    getApplication().getHostServices().showDocument( uri );
110
  }
111
112
  protected InputStream asStream( String resource ) {
113
    return getClass().getResourceAsStream( resource );
114
  }
115
}
1116
A src/main/java/com/scrivenvar/TestVariableNameProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import com.scrivenvar.ui.VariableTreeItem;
31
import java.util.Collection;
32
import java.util.HashMap;
33
import java.util.Map;
34
import static java.util.concurrent.ThreadLocalRandom.current;
35
import java.util.concurrent.TimeUnit;
36
import static java.util.concurrent.TimeUnit.DAYS;
37
import static java.util.concurrent.TimeUnit.HOURS;
38
import static java.util.concurrent.TimeUnit.MILLISECONDS;
39
import static java.util.concurrent.TimeUnit.MINUTES;
40
import static java.util.concurrent.TimeUnit.NANOSECONDS;
41
import static java.util.concurrent.TimeUnit.SECONDS;
42
import static javafx.application.Application.launch;
43
import javafx.scene.control.TreeItem;
44
import javafx.scene.control.TreeView;
45
import javafx.stage.Stage;
46
import org.ahocorasick.trie.*;
47
import org.ahocorasick.trie.Trie.TrieBuilder;
48
import static org.apache.commons.lang.RandomStringUtils.randomNumeric;
49
import org.apache.commons.lang.StringUtils;
50
51
/**
52
 * Tests substituting variable definitions with their values in a swath of text.
53
 *
54
 * @author White Magic Software, Ltd.
55
 */
56
public class TestVariableNameProcessor extends TestHarness {
57
58
  private final static int TEXT_SIZE = 1000000;
59
  private final static int MATCHES_DIVISOR = 1000;
60
61
  private final static StringBuilder SOURCE
62
    = new StringBuilder( randomNumeric( TEXT_SIZE ) );
63
64
  private final static boolean DEBUG = false;
65
66
  public TestVariableNameProcessor() {
67
  }
68
69
  @Override
70
  public void start( final Stage stage ) throws Exception {
71
    super.start( stage );
72
73
    final TreeView<String> treeView = createTreeView();
74
    final Map<String, String> definitions = new HashMap<>();
75
76
    populate( treeView.getRoot(), definitions );
77
    injectVariables( definitions );
78
79
    final String text = SOURCE.toString();
80
81
    show( text );
82
83
    long duration = System.nanoTime();
84
85
    // TODO: Test replaceEach (with intercoluated variables) and replaceEachRepeatedly
86
    // (without intercoluation).
87
    final String result = testBorAhoCorasick( text, definitions );
88
89
    duration = System.nanoTime() - duration;
90
91
    show( result );
92
    System.out.println( elapsed( duration ) );
93
94
    System.exit( 0 );
95
  }
96
97
  private void show( final String s ) {
98
    if( DEBUG ) {
99
      System.out.printf( "%s\n\n", s );
100
    }
101
  }
102
103
  private String testBorAhoCorasick(
104
    final String text,
105
    final Map<String, String> definitions ) {
106
    // Create a buffer sufficiently large that re-allocations are minimized.
107
    final StringBuilder sb = new StringBuilder( text.length() << 1 );
108
109
    final TrieBuilder builder = Trie.builder();
110
    builder.onlyWholeWords();
111
    builder.removeOverlaps();
112
113
    final String[] keys = keys( definitions );
114
115
    for( final String key : keys ) {
116
      builder.addKeyword( key );
117
    }
118
119
    final Trie trie = builder.build();
120
    final Collection<Emit> emits = trie.parseText( text );
121
122
    int prevIndex = 0;
123
124
    for( final Emit emit : emits ) {
125
      final int matchIndex = emit.getStart();
126
127
      sb.append( text.substring( prevIndex, matchIndex ) );
128
      sb.append( definitions.get( emit.getKeyword() ) );
129
      prevIndex = emit.getEnd() + 1;
130
    }
131
132
    // Add the remainder of the string (contains no more matches).
133
    sb.append( text.substring( prevIndex ) );
134
135
    return sb.toString();
136
  }
137
138
  private String testStringUtils(
139
    final String text, final Map<String, String> definitions ) {
140
    final String[] keys = keys( definitions );
141
    final String[] values = values( definitions );
142
143
    return StringUtils.replaceEach( text, keys, values );
144
  }
145
146
  private String[] keys( final Map<String, String> definitions ) {
147
    final int size = definitions.size();
148
    return definitions.keySet().toArray( new String[ size ] );
149
  }
150
151
  private String[] values( final Map<String, String> definitions ) {
152
    final int size = definitions.size();
153
    return definitions.values().toArray( new String[ size ] );
154
  }
155
156
  /**
157
   * Decomposes a period of time into days, hours, minutes, seconds,
158
   * milliseconds, and nanoseconds.
159
   *
160
   * @param duration Time in nanoseconds.
161
   *
162
   * @return A non-null, comma-separated string (without newline).
163
   */
164
  public String elapsed( long duration ) {
165
    final TimeUnit scale = NANOSECONDS;
166
167
    long days = scale.toDays( duration );
168
    duration -= DAYS.toMillis( days );
169
    long hours = scale.toHours( duration );
170
    duration -= HOURS.toMillis( hours );
171
    long minutes = scale.toMinutes( duration );
172
    duration -= MINUTES.toMillis( minutes );
173
    long seconds = scale.toSeconds( duration );
174
    duration -= SECONDS.toMillis( seconds );
175
    long millis = scale.toMillis( duration );
176
    duration -= MILLISECONDS.toMillis( seconds );
177
    long nanos = scale.toNanos( duration );
178
179
    return String.format(
180
      "%d days, %d hours, %d minutes, %d seconds, %d millis, %d nanos",
181
      days, hours, minutes, seconds, millis, nanos
182
    );
183
  }
184
185
  private void injectVariables( final Map<String, String> definitions ) {
186
    for( int i = (SOURCE.length() / MATCHES_DIVISOR) + 1; i > 0; i-- ) {
187
      final int r = current().nextInt( 1, SOURCE.length() );
188
      SOURCE.insert( r, randomKey( definitions ) );
189
    }
190
  }
191
192
  private String randomKey( final Map<String, String> map ) {
193
    final Object[] keys = map.keySet().toArray();
194
    final int r = current().nextInt( keys.length );
195
    return keys[ r ].toString();
196
  }
197
198
  private void populate( final TreeItem<String> parent, final Map<String, String> map ) {
199
    for( final TreeItem<String> child : parent.getChildren() ) {
200
      if( child.isLeaf() ) {
201
        final String key = asDefinition( ((VariableTreeItem<String>)child).toPath() );
202
        final String value = child.getValue();
203
204
        map.put( key, value );
205
      } else {
206
        populate( child, map );
207
      }
208
    }
209
  }
210
211
  private String asDefinition( final String key ) {
212
    return "$" + key + "$";
213
  }
214
215
  public static void main( String[] args ) {
216
    launch( args );
217
  }
218
}
1219
A src/main/java/com/scrivenvar/controls/BrowseDirectoryButton.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import java.io.File;
31
import javafx.event.ActionEvent;
32
import javafx.scene.control.Tooltip;
33
import javafx.stage.DirectoryChooser;
34
import com.scrivenvar.Messages;
35
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
36
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory;
37
38
/**
39
 * Button that opens a directory chooser to select a local directory for a URL in markdown.
40
 *
41
 * @author Karl Tauber
42
 */
43
public class BrowseDirectoryButton
44
	extends BrowseFileButton
45
{
46
	public BrowseDirectoryButton() {
47
		setGraphic(FontAwesomeIconFactory.get().createIcon(FontAwesomeIcon.FOLDER_ALT, "1.2em"));
48
		setTooltip(new Tooltip(Messages.get("BrowseDirectoryButton.tooltip")));
49
	}
50
51
	@Override
52
	protected void browse(ActionEvent e) {
53
		DirectoryChooser directoryChooser = new DirectoryChooser();
54
		directoryChooser.setTitle(Messages.get("BrowseDirectoryButton.chooser.title"));
55
		directoryChooser.setInitialDirectory(getInitialDirectory());
56
		File result = directoryChooser.showDialog(getScene().getWindow());
57
		if (result != null)
58
			updateUrl(result);
59
	}
60
}
161
A src/main/java/com/scrivenvar/controls/BrowseFileButton.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import java.io.File;
31
import java.nio.file.Path;
32
import java.util.ArrayList;
33
import java.util.List;
34
import javafx.beans.property.ObjectProperty;
35
import javafx.beans.property.SimpleObjectProperty;
36
import javafx.event.ActionEvent;
37
import javafx.scene.control.Button;
38
import javafx.scene.control.Tooltip;
39
import javafx.scene.input.KeyCode;
40
import javafx.scene.input.KeyEvent;
41
import javafx.stage.FileChooser;
42
import javafx.stage.FileChooser.ExtensionFilter;
43
import com.scrivenvar.Messages;
44
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
45
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory;
46
47
/**
48
 * Button that opens a file chooser to select a local file for a URL in markdown.
49
 *
50
 * @author Karl Tauber
51
 */
52
public class BrowseFileButton
53
	extends Button
54
{
55
	private final List<ExtensionFilter> extensionFilters = new ArrayList<>();
56
57
	public BrowseFileButton() {
58
		setGraphic(FontAwesomeIconFactory.get().createIcon(FontAwesomeIcon.FILE_ALT, "1.2em"));
59
		setTooltip(new Tooltip(Messages.get("BrowseFileButton.tooltip")));
60
		setOnAction(this::browse);
61
62
		disableProperty().bind(basePath.isNull());
63
64
		// workaround for a JavaFX bug:
65
		//   avoid closing the dialog that contains this control when the user
66
		//   closes the FileChooser or DirectoryChooser using the ESC key
67
		addEventHandler(KeyEvent.KEY_RELEASED, e-> {
68
			if (e.getCode() == KeyCode.ESCAPE)
69
				e.consume();
70
		});
71
	}
72
73
	public void addExtensionFilter(ExtensionFilter extensionFilter) {
74
		extensionFilters.add(extensionFilter);
75
	}
76
77
	// 'basePath' property
78
	private final ObjectProperty<Path> basePath = new SimpleObjectProperty<>();
79
	public Path getBasePath() { return basePath.get(); }
80
	public void setBasePath(Path basePath) { this.basePath.set(basePath); }
81
	public ObjectProperty<Path> basePathProperty() { return basePath; }
82
83
	// 'url' property
84
	private final ObjectProperty<String> url = new SimpleObjectProperty<>();
85
	public String getUrl() { return url.get(); }
86
	public void setUrl(String url) { this.url.set(url); }
87
	public ObjectProperty<String> urlProperty() { return url; }
88
89
	protected void browse(ActionEvent e) {
90
		FileChooser fileChooser = new FileChooser();
91
		fileChooser.setTitle(Messages.get("BrowseFileButton.chooser.title"));
92
		fileChooser.getExtensionFilters().addAll(extensionFilters);
93
		fileChooser.getExtensionFilters().add(new ExtensionFilter(Messages.get("BrowseFileButton.chooser.allFilesFilter"), "*.*"));
94
		fileChooser.setInitialDirectory(getInitialDirectory());
95
		File result = fileChooser.showOpenDialog(getScene().getWindow());
96
		if (result != null)
97
			updateUrl(result);
98
	}
99
100
	protected File getInitialDirectory() {
101
		//TODO build initial directory based on current value of 'url' property
102
		return getBasePath().toFile();
103
	}
104
105
	protected void updateUrl(File file) {
106
		String newUrl;
107
		try {
108
			newUrl = getBasePath().relativize(file.toPath()).toString();
109
		} catch (IllegalArgumentException ex) {
110
			newUrl = file.toString();
111
		}
112
		url.set(newUrl.replace('\\', '/'));
113
	}
114
}
1115
A src/main/java/com/scrivenvar/controls/EscapeTextField.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import javafx.beans.property.SimpleStringProperty;
31
import javafx.beans.property.StringProperty;
32
import javafx.scene.control.TextField;
33
import javafx.util.StringConverter;
34
import com.scrivenvar.util.Utils;
35
36
/**
37
 * TextField that can escape/unescape characters for markdown.
38
 *
39
 * @author Karl Tauber
40
 */
41
public class EscapeTextField
42
	extends TextField
43
{
44
	public EscapeTextField() {
45
		escapedText.bindBidirectional(textProperty(), new StringConverter<String>() {
46
			@Override public String toString(String object) { return escape(object); }
47
			@Override public String fromString(String string) { return unescape(string); }
48
		});
49
		escapeCharacters.addListener(e -> escapedText.set(escape(textProperty().get())));
50
	}
51
52
	// 'escapedText' property
53
	private final StringProperty escapedText = new SimpleStringProperty();
54
	public String getEscapedText() { return escapedText.get(); }
55
	public void setEscapedText(String escapedText) { this.escapedText.set(escapedText); }
56
	public StringProperty escapedTextProperty() { return escapedText; }
57
58
	// 'escapeCharacters' property
59
	private final StringProperty escapeCharacters = new SimpleStringProperty();
60
	public String getEscapeCharacters() { return escapeCharacters.get(); }
61
	public void setEscapeCharacters(String escapeCharacters) { this.escapeCharacters.set(escapeCharacters); }
62
	public StringProperty escapeCharactersProperty() { return escapeCharacters; }
63
64
	private String escape(String s) {
65
		String escapeChars = getEscapeCharacters();
66
		return !Utils.isNullOrEmpty(escapeChars)
67
				? s.replaceAll("([" + escapeChars.replaceAll("(.)", "\\\\$1") + "])", "\\\\$1")
68
				: s;
69
	}
70
71
	private String unescape(String s) {
72
		String escapeChars = getEscapeCharacters();
73
		return !Utils.isNullOrEmpty(escapeChars)
74
				? s.replaceAll("\\\\([" + escapeChars.replaceAll("(.)", "\\\\$1") + "])", "$1")
75
				: s;
76
	}
77
}
178
A src/main/java/com/scrivenvar/controls/FlagCheckBox.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.controls;
28
29
import javafx.beans.property.IntegerProperty;
30
import javafx.beans.property.SimpleIntegerProperty;
31
import javafx.scene.control.CheckBox;
32
33
/**
34
 * CheckBox that toggles a bit in an integer.
35
 *
36
 * @author Karl Tauber
37
 */
38
public class FlagCheckBox extends CheckBox {
39
40
  public FlagCheckBox() {
41
    setOnAction( e -> {
42
      if( isSelected() ) {
43
        setFlags( getFlags() | getFlag() );
44
      } else {
45
        setFlags( getFlags() & ~getFlag() );
46
      }
47
    } );
48
49
    flags.addListener( (obs, oldFlags, newFlags) -> {
50
      setSelected( (newFlags.intValue() & getFlag()) != 0 );
51
    } );
52
  }
53
54
  // 'flag' property
55
  private final IntegerProperty flag = new SimpleIntegerProperty();
56
57
  public int getFlag() {
58
    return flag.get();
59
  }
60
61
  public void setFlag( int flag ) {
62
    this.flag.set( flag );
63
  }
64
65
  public IntegerProperty flagProperty() {
66
    return flag;
67
  }
68
69
  // 'flags' property
70
  private final IntegerProperty flags = new SimpleIntegerProperty();
71
72
  public int getFlags() {
73
    return flags.get();
74
  }
75
76
  public void setFlags( int flags ) {
77
    this.flags.set( flags );
78
  }
79
80
  public IntegerProperty flagsProperty() {
81
    return flags;
82
  }
83
}
184
A src/main/java/com/scrivenvar/controls/WebHyperlink.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import javafx.beans.property.SimpleStringProperty;
31
import javafx.beans.property.StringProperty;
32
import javafx.scene.control.Hyperlink;
33
import com.scrivenvar.Main;
34
35
/**
36
 * Opens a web site in the default web browser.
37
 *
38
 * @author Karl Tauber
39
 */
40
public class WebHyperlink
41
	extends Hyperlink
42
{
43
	public WebHyperlink() {
44
		setStyle("-fx-padding: 0; -fx-border-width: 0");
45
	}
46
47
	@Override
48
	public void fire() {
49
		Main.showDocument(getUri());
50
	}
51
52
	// 'uri' property
53
	private final StringProperty uri = new SimpleStringProperty();
54
	public String getUri() { return uri.get(); }
55
	public void setUri(String uri) { this.uri.set(uri); }
56
	public StringProperty UriProperty() { return uri; }
57
}
158
A src/main/java/com/scrivenvar/decorators/RVariableDecorator.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Brackets variable names with <code>`r#</code> and <code>`</code>.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class RVariableDecorator implements VariableDecorator {
36
37
  /**
38
   * Returns the given string R-escaping backticks prepended and appended. This
39
   * is not null safe. Do not pass null into this method.
40
   *
41
   * @param variableName The string to decorate.
42
   *
43
   * @return "`r#" + variableName + "`".
44
   */
45
  @Override
46
  public String decorate( final String variableName ) {
47
    return "`r#" + variableName + "`";
48
  }
49
}
150
A src/main/java/com/scrivenvar/decorators/VariableDecorator.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Responsible for updating variable names to use a machine-readable format
32
 * corresponding to the type of file being edited.
33
 */
34
public interface VariableDecorator {
35
36
  /**
37
   * This decorates a variable name based on some criteria determined by the
38
   * factory that creates implementations of this interface.
39
   *
40
   * @param variableName The text to decorate as per the filename extension
41
   * would indicate (e.g., ".md" goes to $VAR$ while ".Rmd" goes to `r#VAR`).
42
   *
43
   * @return The given variable name modified with its requisite delimiters.
44
   */
45
  public String decorate( String variableName );
46
}
147
A src/main/java/com/scrivenvar/decorators/YamlVariableDecorator.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Brackets variable names with dollar symbols.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class YamlVariableDecorator implements VariableDecorator {
36
37
  /**
38
   * Matches variables delimited by dollar symbols. The outer group is necessary
39
   * for substring replacement of delimited references.
40
   */
41
  public final static String REGEX = "(\\$(.*?)\\$)";
42
43
  /**
44
   * Returns the given string with a $ symbol prepended and appended. This is
45
   * not null safe. Do not pass null into this method.
46
   *
47
   * @param variableName The string to decorate.
48
   *
49
   * @return "$" + variableName + "$".
50
   */
51
  @Override
52
  public String decorate( final String variableName ) {
53
    return "$" + variableName + "$";
54
  }
55
}
156
A src/main/java/com/scrivenvar/definition/DefinitionPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import static com.scrivenvar.Constants.SEPARATOR;
31
import static com.scrivenvar.definition.Lists.getFirst;
32
import com.scrivenvar.predicates.strings.ContainsPredicate;
33
import com.scrivenvar.predicates.strings.StartsPredicate;
34
import com.scrivenvar.predicates.strings.StringPredicate;
35
import com.scrivenvar.ui.AbstractPane;
36
import com.scrivenvar.ui.VariableTreeItem;
37
import java.util.List;
38
import javafx.collections.ObservableList;
39
import javafx.scene.Node;
40
import javafx.scene.control.MultipleSelectionModel;
41
import javafx.scene.control.SelectionMode;
42
import javafx.scene.control.TreeItem;
43
import javafx.scene.control.TreeView;
44
45
/**
46
 * Provides a list of variables that can be referenced in the editor.
47
 *
48
 * @author White Magic Software, Ltd.
49
 */
50
public class DefinitionPane extends AbstractPane {
51
52
  private final static String TERMINALS = ":;,.!?-/\\¡¿";
53
54
  private TreeView<String> treeView;
55
56
  /**
57
   * Constructs a definition pane with a given tree view root.
58
   *
59
   * @see YamlTreeAdapter.adapt
60
   * @param root The root of the variable definition tree.
61
   */
62
  public DefinitionPane( final TreeView<String> root ) {
63
    setTreeView( root );
64
    initTreeView();
65
  }
66
67
  /**
68
   * Finds a tree item with a value that exactly matches the given word.
69
   *
70
   * @param trunk The root item containing a list of nodes to search.
71
   * @param word The value of the item to find.
72
   * @param predicate Helps determine whether the node value matches the word.
73
   *
74
   * @return The item that matches the given word, or null if not found.
75
   */
76
  private TreeItem<String> findNode(
77
    final TreeItem<String> trunk,
78
    final StringPredicate predicate ) {
79
    final List<TreeItem<String>> branches = trunk.getChildren();
80
    TreeItem<String> result = null;
81
82
    for( final TreeItem<String> leaf : branches ) {
83
      if( predicate.test( leaf.getValue() ) ) {
84
        result = leaf;
85
        break;
86
      }
87
    }
88
89
    return result;
90
  }
91
92
  /**
93
   * Calls findNode with the EqualsPredicate.
94
   *
95
   * @see findNode( TreeItem, String, Predicate )
96
   * @return The result from findNode.
97
   */
98
  private TreeItem<String> findStartsNode(
99
    final TreeItem<String> trunk,
100
    final String word ) {
101
    return findNode( trunk, new StartsPredicate( word ) );
102
  }
103
104
  /**
105
   * Calls findNode with the ContainsPredicate.
106
   *
107
   * @see findNode( TreeItem, String, Predicate )
108
   * @return The result from findNode.
109
   */
110
  private TreeItem<String> findSubstringNode(
111
    final TreeItem<String> trunk,
112
    final String word ) {
113
    return findNode( trunk, new ContainsPredicate( word ) );
114
  }
115
116
  /**
117
   * Finds a node that matches a prefix and suffix specified by the given path
118
   * variable. The prefix must match a valid node value. The suffix refers to
119
   * the start of a string that matches zero or more children of the node
120
   * specified by the prefix. The algorithm has the following cases:
121
   *
122
   * <ol>
123
   * <li>Path is empty, return first child.</li>
124
   * <li>Path contains a complete match, return corresponding node.</li>
125
   * <li>Path contains a partial match, return nearest node.</li>
126
   * <li>Path contains a complete and partial match, return nearest node.</li>
127
   * </ol>
128
   *
129
   * @param path The word typed by the user, which contains dot-separated node
130
   * names that represent a path within the YAML tree plus a partial variable
131
   * name match (for a node).
132
   *
133
   * @return The node value that starts with the suffix portion of the given
134
   * path, never null.
135
   */
136
  public TreeItem<String> findNode( String path ) {
137
    TreeItem<String> cItem = getTreeRoot();
138
    TreeItem<String> pItem = cItem;
139
140
    int index = path.indexOf( SEPARATOR );
141
142
    while( index >= 0 ) {
143
      final String node = path.substring( 0, index );
144
      path = path.substring( index + 1 );
145
146
      if( (cItem = findStartsNode( cItem, node )) == null ) {
147
        break;
148
      }
149
150
      index = path.indexOf( SEPARATOR );
151
      pItem = cItem;
152
    }
153
154
    // Find the node that starts with whatever the user typed.
155
    cItem = findStartsNode( pItem, path );
156
157
    // If there was no matching node, then find a substring match.
158
    if( cItem == null ) {
159
      cItem = findSubstringNode( pItem, path );
160
    }
161
162
    // If neither starts with nor substring matched a node, revert to the last
163
    // known valid node.
164
    if( cItem == null ) {
165
      cItem = pItem;
166
    }
167
168
    return sanitize( cItem );
169
  }
170
171
  /**
172
   * Returns the leaf that matches the given value. If the value is terminally
173
   * punctuated, the punctuation is removed if no match was found.
174
   *
175
   * @param value The value to find, never null.
176
   *
177
   * @return The leaf that contains the given value, or null if neither the
178
   * original value nor the terminally-trimmed value was found.
179
   */
180
  public VariableTreeItem<String> findLeaf( final String value ) {
181
    final VariableTreeItem<String> root = getTreeRoot();
182
    final VariableTreeItem<String> leaf = root.findLeaf( value );
183
184
    return leaf == null
185
      ? root.findLeaf( rtrimTerminalPunctuation( value ) )
186
      : leaf;
187
  }
188
189
  /**
190
   * Removes punctuation from the end of a string. The character set includes:
191
   * <code>:;,.!?-/\¡¿</code>.
192
   *
193
   * @param s The string to trim, never null.
194
   *
195
   * @return The string trimmed of all terminal characters from the end
196
   */
197
  private String rtrimTerminalPunctuation( final String s ) {
198
    final StringBuilder result = new StringBuilder( s.trim() );
199
200
    while( TERMINALS.contains( "" + result.charAt( result.length() - 1 ) ) ) {
201
      result.setLength( result.length() - 1 );
202
    }
203
204
    return result.toString();
205
  }
206
207
  /**
208
   * Returns the tree root if either item or its first child are null.
209
   *
210
   * @param item The item to make null safe.
211
   *
212
   * @return A non-null TreeItem, possibly the root item (to avoid null).
213
   */
214
  private TreeItem<String> sanitize( final TreeItem<String> item ) {
215
    final TreeItem<String> result = item == getTreeRoot()
216
      ? getFirst( item.getChildren() )
217
      : item;
218
219
    return result == null ? item : result;
220
  }
221
222
  /**
223
   * Expands the node to the root, recursively.
224
   *
225
   * @param <T> The type of tree item to expand (usually String).
226
   * @param node The node to expand.
227
   */
228
  public <T> void expand( final TreeItem<T> node ) {
229
    if( node != null ) {
230
      expand( node.getParent() );
231
232
      if( !node.isLeaf() ) {
233
        node.setExpanded( true );
234
      }
235
    }
236
  }
237
238
  public void select( final TreeItem<String> item ) {
239
    clearSelection();
240
    selectItem( getTreeView().getRow( item ) );
241
  }
242
243
  private void clearSelection() {
244
    getSelectionModel().clearSelection();
245
  }
246
247
  private void selectItem( final int row ) {
248
    getSelectionModel().select( row );
249
  }
250
251
  /**
252
   * Collapses the tree, recursively.
253
   */
254
  public void collapse() {
255
    collapse( getTreeRoot().getChildren() );
256
  }
257
258
  /**
259
   * Collapses the tree, recursively.
260
   *
261
   * @param <T> The type of tree item to expand (usually String).
262
   * @param node The nodes to collapse.
263
   */
264
  private <T> void collapse( ObservableList<TreeItem<T>> nodes ) {
265
    for( final TreeItem<T> node : nodes ) {
266
      node.setExpanded( false );
267
      collapse( node.getChildren() );
268
    }
269
  }
270
271
  private void initTreeView() {
272
    getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
273
  }
274
275
  /**
276
   * Returns the root node to the tree view.
277
   *
278
   * @return getTreeView()
279
   */
280
  public Node getNode() {
281
    return getTreeView();
282
  }
283
284
  private MultipleSelectionModel getSelectionModel() {
285
    return getTreeView().getSelectionModel();
286
  }
287
288
  /**
289
   * Returns the tree view that contains the YAML definition hierarchy.
290
   *
291
   * @return A non-null instance.
292
   */
293
  private TreeView<String> getTreeView() {
294
    return this.treeView;
295
  }
296
297
  /**
298
   * Returns the root of the tree.
299
   *
300
   * @return The first node added to the YAML definition tree.
301
   */
302
  private VariableTreeItem<String> getTreeRoot() {
303
    return (VariableTreeItem<String>)getTreeView().getRoot();
304
  }
305
306
  public <T> boolean isRoot( final TreeItem<T> item ) {
307
    return getTreeRoot().equals( item );
308
  }
309
310
  /**
311
   * Sets the tree view (called by the constructor).
312
   *
313
   * @param treeView
314
   */
315
  private void setTreeView( TreeView<String> treeView ) {
316
    if( treeView != null ) {
317
      this.treeView = treeView;
318
    }
319
  }
320
}
1321
A src/main/java/com/scrivenvar/definition/Lists.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import java.util.List;
31
32
/**
33
 * Convenience class that provides a clearer API for obtaining list elements.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public final class Lists {
38
39
  private Lists() {
40
  }
41
42
  /**
43
   * Returns the first item in the given list, or null if not found.
44
   *
45
   * @param <T> The generic list type.
46
   * @param list The list that may have a first item.
47
   *
48
   * @return null if the list is null or there is no first item.
49
   */
50
  public static <T> T getFirst( final List<T> list ) {
51
    return getFirst( list, null );
52
  }
53
54
  /**
55
   * Returns the last item in the given list, or null if not found.
56
   *
57
   * @param <T> The generic list type.
58
   * @param list The list that may have a last item.
59
   *
60
   * @return null if the list is null or there is no last item.
61
   */
62
  public static <T> T getLast( final List<T> list ) {
63
    return getLast( list, null );
64
  }
65
66
  /**
67
   * Returns the first item in the given list, or t if not found.
68
   *
69
   * @param <T> The generic list type.
70
   * @param list The list that may have a first item.
71
   * @param t The default return value.
72
   *
73
   * @return null if the list is null or there is no first item.
74
   */
75
  public static <T> T getFirst( final List<T> list, final T t ) {
76
    return isEmpty( list ) ? t : list.get( 0 );
77
  }
78
79
  /**
80
   * Returns the last item in the given list, or t if not found.
81
   *
82
   * @param <T> The generic list type.
83
   * @param list The list that may have a last item.
84
   * @param t The default return value.
85
   *
86
   * @return null if the list is null or there is no last item.
87
   */
88
  public static <T> T getLast( final List<T> list, final T t ) {
89
    return isEmpty( list ) ? t : list.get( list.size() - 1 );
90
  }
91
92
  /**
93
   * Returns true if the given list is null or empty.
94
   *
95
   * @param <T> The generic list type.
96
   * @param list The list that has a last item.
97
   *
98
   * @return true The list is empty.
99
   */
100
  public static <T> boolean isEmpty( final List<T> list ) {
101
    return list == null || list.isEmpty();
102
  }
103
}
1104
A src/main/java/com/scrivenvar/dialogs/ImageDialog.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.dialogs;
28
29
import com.scrivenvar.Messages;
30
import com.scrivenvar.controls.BrowseFileButton;
31
import com.scrivenvar.controls.EscapeTextField;
32
import com.scrivenvar.service.events.impl.ButtonOrderPane;
33
import java.nio.file.Path;
34
import javafx.application.Platform;
35
import javafx.beans.binding.Bindings;
36
import javafx.beans.property.SimpleStringProperty;
37
import javafx.beans.property.StringProperty;
38
import javafx.scene.control.ButtonBar.ButtonData;
39
import javafx.scene.control.ButtonType;
40
import javafx.scene.control.Dialog;
41
import javafx.scene.control.DialogPane;
42
import javafx.scene.control.Label;
43
import javafx.stage.FileChooser.ExtensionFilter;
44
import javafx.stage.Window;
45
import org.tbee.javafx.scene.layout.fxml.MigPane;
46
47
/**
48
 * Dialog to enter a markdown image.
49
 *
50
 * @author Karl Tauber
51
 */
52
public class ImageDialog extends Dialog<String> {
53
54
  private final StringProperty image = new SimpleStringProperty();
55
56
  public ImageDialog( Window owner, Path basePath ) {
57
    setTitle( Messages.get( "ImageDialog.title" ) );
58
    initOwner( owner );
59
    setResizable( true );
60
61
    initComponents();
62
63
    linkBrowseFileButton.setBasePath( basePath );
64
    linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( Messages.get( "ImageDialog.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) );
65
    linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() );
66
67
    setDialogPane( new ButtonOrderPane() );
68
    final DialogPane dialogPane = getDialogPane();
69
    dialogPane.setContent( pane );
70
    dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
71
72
    dialogPane.lookupButton( ButtonType.OK ).disableProperty().bind(
73
      urlField.escapedTextProperty().isEmpty()
74
      .or( textField.escapedTextProperty().isEmpty() ) );
75
76
    image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() )
77
      .then( Bindings.format( "![%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) )
78
      .otherwise( Bindings.format( "![%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) );
79
    previewField.textProperty().bind( image );
80
81
    setResultConverter( dialogButton -> {
82
      ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null;
83
      return (data == ButtonData.OK_DONE) ? image.get() : null;
84
    } );
85
86
    Platform.runLater( () -> {
87
      urlField.requestFocus();
88
89
      if( urlField.getText().startsWith( "http://" ) ) {
90
        urlField.selectRange( "http://".length(), urlField.getLength() );
91
      }
92
    } );
93
  }
94
95
  private void initComponents() {
96
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
97
    pane = new MigPane();
98
    Label urlLabel = new Label();
99
    urlField = new EscapeTextField();
100
    linkBrowseFileButton = new BrowseFileButton();
101
    Label textLabel = new Label();
102
    textField = new EscapeTextField();
103
    Label titleLabel = new Label();
104
    titleField = new EscapeTextField();
105
    Label previewLabel = new Label();
106
    previewField = new Label();
107
108
    //======== pane ========
109
    {
110
      pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" );
111
      pane.setRows( "[][][][]" );
112
113
      //---- urlLabel ----
114
      urlLabel.setText( Messages.get( "ImageDialog.urlLabel.text" ) );
115
      pane.add( urlLabel, "cell 0 0" );
116
117
      //---- urlField ----
118
      urlField.setEscapeCharacters( "()" );
119
      urlField.setText( "http://yourlink.com" );
120
      urlField.setPromptText( "http://yourlink.com" );
121
      pane.add( urlField, "cell 1 0" );
122
      pane.add( linkBrowseFileButton, "cell 2 0" );
123
124
      //---- textLabel ----
125
      textLabel.setText( Messages.get( "ImageDialog.textLabel.text" ) );
126
      pane.add( textLabel, "cell 0 1" );
127
128
      //---- textField ----
129
      textField.setEscapeCharacters( "[]" );
130
      pane.add( textField, "cell 1 1 2 1" );
131
132
      //---- titleLabel ----
133
      titleLabel.setText( Messages.get( "ImageDialog.titleLabel.text" ) );
134
      pane.add( titleLabel, "cell 0 2" );
135
      pane.add( titleField, "cell 1 2 2 1" );
136
137
      //---- previewLabel ----
138
      previewLabel.setText( Messages.get( "ImageDialog.previewLabel.text" ) );
139
      pane.add( previewLabel, "cell 0 3" );
140
      pane.add( previewField, "cell 1 3 2 1" );
141
    }
142
    // JFormDesigner - End of component initialization  //GEN-END:initComponents
143
  }
144
145
  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
146
  private MigPane pane;
147
  private EscapeTextField urlField;
148
  private BrowseFileButton linkBrowseFileButton;
149
  private EscapeTextField textField;
150
  private EscapeTextField titleField;
151
  private Label previewField;
152
	// JFormDesigner - End of variables declaration  //GEN-END:variables
153
}
1154
A src/main/java/com/scrivenvar/dialogs/ImageDialog.jfd
1
JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8"
2
3
new FormModel {
4
	"i18n.bundlePackage": "com.scrivendor"
5
	"i18n.bundleName": "messages"
6
	"i18n.autoExternalize": true
7
	"i18n.keyPrefix": "ImageDialog"
8
	contentType: "form/javafx"
9
	root: new FormRoot {
10
		add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
11
			"$layoutConstraints": ""
12
			"$columnConstraints": "[shrink 0,fill][300,grow,fill][fill]"
13
			"$rowConstraints": "[][][][]"
14
		} ) {
15
			name: "pane"
16
			add( new FormComponent( "javafx.scene.control.Label" ) {
17
				name: "urlLabel"
18
				"text": new FormMessage( null, "ImageDialog.urlLabel.text" )
19
				auxiliary() {
20
					"JavaCodeGenerator.variableLocal": true
21
				}
22
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
23
				"value": "cell 0 0"
24
			} )
25
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
26
				name: "urlField"
27
				"escapeCharacters": "()"
28
				"text": "http://yourlink.com"
29
				"promptText": "http://yourlink.com"
30
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
31
				"value": "cell 1 0"
32
			} )
33
			add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) {
34
				name: "linkBrowseFileButton"
35
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
36
				"value": "cell 2 0"
37
			} )
38
			add( new FormComponent( "javafx.scene.control.Label" ) {
39
				name: "textLabel"
40
				"text": new FormMessage( null, "ImageDialog.textLabel.text" )
41
				auxiliary() {
42
					"JavaCodeGenerator.variableLocal": true
43
				}
44
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
45
				"value": "cell 0 1"
46
			} )
47
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
48
				name: "textField"
49
				"escapeCharacters": "[]"
50
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
51
				"value": "cell 1 1 2 1"
52
			} )
53
			add( new FormComponent( "javafx.scene.control.Label" ) {
54
				name: "titleLabel"
55
				"text": new FormMessage( null, "ImageDialog.titleLabel.text" )
56
				auxiliary() {
57
					"JavaCodeGenerator.variableLocal": true
58
				}
59
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
60
				"value": "cell 0 2"
61
			} )
62
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
63
				name: "titleField"
64
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
65
				"value": "cell 1 2 2 1"
66
			} )
67
			add( new FormComponent( "javafx.scene.control.Label" ) {
68
				name: "previewLabel"
69
				"text": new FormMessage( null, "ImageDialog.previewLabel.text" )
70
				auxiliary() {
71
					"JavaCodeGenerator.variableLocal": true
72
				}
73
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
74
				"value": "cell 0 3"
75
			} )
76
			add( new FormComponent( "javafx.scene.control.Label" ) {
77
				name: "previewField"
78
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
79
				"value": "cell 1 3 2 1"
80
			} )
81
		}, new FormLayoutConstraints( null ) {
82
			"location": new javafx.geometry.Point2D( 0.0, 0.0 )
83
			"size": new javafx.geometry.Dimension2D( 500.0, 300.0 )
84
		} )
85
	}
86
}
187
A src/main/java/com/scrivenvar/dialogs/LinkDialog.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.dialogs;
29
30
import com.scrivenvar.Messages;
31
import com.scrivenvar.controls.EscapeTextField;
32
import com.scrivenvar.editor.HyperlinkModel;
33
import com.scrivenvar.service.events.impl.ButtonOrderPane;
34
import java.nio.file.Path;
35
import javafx.application.Platform;
36
import javafx.beans.binding.Bindings;
37
import javafx.beans.property.SimpleStringProperty;
38
import javafx.beans.property.StringProperty;
39
import javafx.scene.control.ButtonBar.ButtonData;
40
import javafx.scene.control.ButtonType;
41
import javafx.scene.control.Dialog;
42
import javafx.scene.control.DialogPane;
43
import javafx.scene.control.Label;
44
import javafx.stage.Window;
45
import org.tbee.javafx.scene.layout.fxml.MigPane;
46
47
/**
48
 * Dialog to enter a markdown link.
49
 *
50
 * @author Karl Tauber
51
 */
52
public class LinkDialog extends Dialog<String> {
53
54
  private final StringProperty link = new SimpleStringProperty();
55
56
  public LinkDialog( final Window owner, final HyperlinkModel hyperlink, final Path basePath ) {
57
    setTitle( Messages.get( "LinkDialog.title" ) );
58
    initOwner( owner );
59
    setResizable( true );
60
61
    initComponents();
62
63
    setDialogPane( new ButtonOrderPane() );
64
65
    final DialogPane dialog = getDialogPane();
66
    dialog.setContent( pane );
67
    dialog.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
68
69
    dialog.lookupButton( ButtonType.OK ).disableProperty().bind(
70
      urlField.escapedTextProperty().isEmpty() );
71
72
    textField.setText( hyperlink.getText() );
73
    urlField.setText( hyperlink.getUrl() );
74
    titleField.setText( hyperlink.getTitle() );
75
76
    link.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() )
77
      .then( Bindings.format( "[%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) )
78
      .otherwise( Bindings.when( textField.escapedTextProperty().isNotEmpty() )
79
        .then( Bindings.format( "[%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) )
80
        .otherwise( urlField.escapedTextProperty() ) ) );
81
82
    setResultConverter( dialogButton -> {
83
      ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null;
84
      return (data == ButtonData.OK_DONE) ? link.get() : null;
85
    } );
86
87
    Platform.runLater( () -> {
88
      urlField.requestFocus();
89
      urlField.selectRange( 0, urlField.getLength() );
90
    } );
91
  }
92
93
  private void initComponents() {
94
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
95
    pane = new MigPane();
96
    Label urlLabel = new Label();
97
    urlField = new EscapeTextField();
98
    Label textLabel = new Label();
99
    textField = new EscapeTextField();
100
    Label titleLabel = new Label();
101
    titleField = new EscapeTextField();
102
103
    //======== pane ========
104
    {
105
      pane.setCols( "[shrink 0,fill][300,grow,fill][fill][fill]" );
106
      pane.setRows( "[][][][]" );
107
108
      //---- urlLabel ----
109
      urlLabel.setText( Messages.get( "LinkDialog.urlLabel.text" ) );
110
      pane.add( urlLabel, "cell 0 0" );
111
112
      //---- urlField ----
113
      urlField.setEscapeCharacters( "()" );
114
      pane.add( urlField, "cell 1 0" );
115
116
      //---- textLabel ----
117
      textLabel.setText( Messages.get( "LinkDialog.textLabel.text" ) );
118
      pane.add( textLabel, "cell 0 1" );
119
120
      //---- textField ----
121
      textField.setEscapeCharacters( "[]" );
122
      pane.add( textField, "cell 1 1 3 1" );
123
124
      //---- titleLabel ----
125
      titleLabel.setText( Messages.get( "LinkDialog.titleLabel.text" ) );
126
      pane.add( titleLabel, "cell 0 2" );
127
      pane.add( titleField, "cell 1 2 3 1" );
128
    }
129
    // JFormDesigner - End of component initialization  //GEN-END:initComponents
130
  }
131
132
  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
133
  private MigPane pane;
134
  private EscapeTextField urlField;
135
  private EscapeTextField textField;
136
  private EscapeTextField titleField;
137
  // JFormDesigner - End of variables declaration  //GEN-END:variables
138
}
1139
A src/main/java/com/scrivenvar/dialogs/LinkDialog.jfd
1
JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8"
2
3
new FormModel {
4
	"i18n.bundlePackage": "com.scrivendor"
5
	"i18n.bundleName": "messages"
6
	"i18n.autoExternalize": true
7
	"i18n.keyPrefix": "LinkDialog"
8
	contentType: "form/javafx"
9
	root: new FormRoot {
10
		add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
11
			"$layoutConstraints": ""
12
			"$columnConstraints": "[shrink 0,fill][300,grow,fill][fill][fill]"
13
			"$rowConstraints": "[][][][]"
14
		} ) {
15
			name: "pane"
16
			add( new FormComponent( "javafx.scene.control.Label" ) {
17
				name: "urlLabel"
18
				"text": new FormMessage( null, "LinkDialog.urlLabel.text" )
19
				auxiliary() {
20
					"JavaCodeGenerator.variableLocal": true
21
				}
22
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
23
				"value": "cell 0 0"
24
			} )
25
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
26
				name: "urlField"
27
				"escapeCharacters": "()"
28
				"text": "http://yourlink.com"
29
				"promptText": "http://yourlink.com"
30
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
31
				"value": "cell 1 0"
32
			} )
33
			add( new FormComponent( "com.scrivendor.controls.BrowseDirectoryButton" ) {
34
				name: "linkBrowseDirectoyButton"
35
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
36
				"value": "cell 2 0"
37
			} )
38
			add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) {
39
				name: "linkBrowseFileButton"
40
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
41
				"value": "cell 3 0"
42
			} )
43
			add( new FormComponent( "javafx.scene.control.Label" ) {
44
				name: "textLabel"
45
				"text": new FormMessage( null, "LinkDialog.textLabel.text" )
46
				auxiliary() {
47
					"JavaCodeGenerator.variableLocal": true
48
				}
49
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
50
				"value": "cell 0 1"
51
			} )
52
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
53
				name: "textField"
54
				"escapeCharacters": "[]"
55
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
56
				"value": "cell 1 1 3 1"
57
			} )
58
			add( new FormComponent( "javafx.scene.control.Label" ) {
59
				name: "titleLabel"
60
				"text": new FormMessage( null, "LinkDialog.titleLabel.text" )
61
				auxiliary() {
62
					"JavaCodeGenerator.variableLocal": true
63
				}
64
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
65
				"value": "cell 0 2"
66
			} )
67
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
68
				name: "titleField"
69
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
70
				"value": "cell 1 2 3 1"
71
			} )
72
			add( new FormComponent( "javafx.scene.control.Label" ) {
73
				name: "previewLabel"
74
				"text": new FormMessage( null, "LinkDialog.previewLabel.text" )
75
				auxiliary() {
76
					"JavaCodeGenerator.variableLocal": true
77
				}
78
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
79
				"value": "cell 0 3"
80
			} )
81
			add( new FormComponent( "javafx.scene.control.Label" ) {
82
				name: "previewField"
83
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
84
				"value": "cell 1 3 3 1"
85
			} )
86
		}, new FormLayoutConstraints( null ) {
87
			"location": new javafx.geometry.Point2D( 0.0, 0.0 )
88
			"size": new javafx.geometry.Dimension2D( 500.0, 300.0 )
89
		} )
90
	}
91
}
192
A src/main/java/com/scrivenvar/editor/EditorPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editor;
29
30
import com.scrivenvar.ui.AbstractPane;
31
import java.nio.file.Path;
32
import java.util.function.Consumer;
33
import javafx.application.Platform;
34
import javafx.beans.property.ObjectProperty;
35
import javafx.beans.property.SimpleObjectProperty;
36
import javafx.beans.value.ChangeListener;
37
import javafx.event.Event;
38
import javafx.scene.control.ScrollPane;
39
import javafx.scene.input.InputEvent;
40
import org.fxmisc.flowless.VirtualizedScrollPane;
41
import org.fxmisc.richtext.StyleClassedTextArea;
42
import org.fxmisc.undo.UndoManager;
43
import org.fxmisc.wellbehaved.event.EventPattern;
44
import org.fxmisc.wellbehaved.event.InputMap;
45
import static org.fxmisc.wellbehaved.event.InputMap.consume;
46
import org.fxmisc.wellbehaved.event.Nodes;
47
48
/**
49
 * Represents common editing features for various types of text editors.
50
 *
51
 * @author White Magic Software, Ltd.
52
 */
53
public class EditorPane extends AbstractPane {
54
55
  private StyleClassedTextArea editor;
56
  private VirtualizedScrollPane<StyleClassedTextArea> scrollPane;
57
  private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
58
59
  /**
60
   * Set when entering variable edit mode; retrieved upon exiting.
61
   */
62
  private InputMap<InputEvent> nodeMap;
63
64
  @Override
65
  public void requestFocus() {
66
    Platform.runLater( () -> getEditor().requestFocus() );
67
  }
68
69
  public void undo() {
70
    getUndoManager().undo();
71
  }
72
73
  public void redo() {
74
    getUndoManager().redo();
75
  }
76
77
  public UndoManager getUndoManager() {
78
    return getEditor().getUndoManager();
79
  }
80
81
  public String getText() {
82
    return getEditor().getText();
83
  }
84
85
  public void setText( final String text ) {
86
    getEditor().deselect();
87
    getEditor().replaceText( text );
88
    getUndoManager().mark();
89
  }
90
91
  /**
92
   * Call to hook into changes to the text area.
93
   *
94
   * @param listener Receives editor text change events.
95
   */
96
  public void addTextChangeListener( final ChangeListener<? super String> listener ) {
97
    getEditor().textProperty().addListener( listener );
98
  }
99
100
  /**
101
   * Call to listen for when the caret moves to another paragraph.
102
   * 
103
   * @param listener Receives paragraph change events.
104
   */
105
  public void addCaretParagraphListener(
106
    final ChangeListener<? super Integer> listener ) {
107
    getEditor().currentParagraphProperty().addListener( listener );
108
  }
109
  
110
  /**
111
   * This method adds listeners to editor events.
112
   *
113
   * @param <T> The event type.
114
   * @param <U> The consumer type for the given event type.
115
   * @param event The event of interest.
116
   * @param consumer The method to call when the event happens.
117
   */
118
  public <T extends Event, U extends T> void addEventListener(
119
    final EventPattern<? super T, ? extends U> event,
120
    final Consumer<? super U> consumer ) {
121
    Nodes.addInputMap( getEditor(), consume( event, consumer ) );
122
  }
123
124
  /**
125
   * This method adds listeners to editor events that can be removed without
126
   * affecting the original listeners (i.e., the original lister is restored on
127
   * a call to removeEventListener).
128
   *
129
   * @param map The map of methods to events.
130
   */
131
  @SuppressWarnings( "unchecked" )
132
  public void addEventListener( final InputMap<InputEvent> map ) {
133
    this.nodeMap = (InputMap<InputEvent>)getInputMap();
134
    Nodes.addInputMap( getEditor(), map );
135
  }
136
137
  /**
138
   * This method removes listeners to editor events and restores the default
139
   * handler.
140
   *
141
   * @param map The map of methods to events.
142
   */
143
  public void removeEventListener( final InputMap<InputEvent> map ) {
144
    Nodes.removeInputMap( getEditor(), map );
145
    Nodes.addInputMap( getEditor(), this.nodeMap );
146
  }
147
148
  /**
149
   * Returns the value for "org.fxmisc.wellbehaved.event.inputmap".
150
   *
151
   * @return An input map of input events.
152
   */
153
  private Object getInputMap() {
154
    return getEditor().getProperties().get( getInputMapKey() );
155
  }
156
157
  /**
158
   * Returns the hashmap key entry for the input map.
159
   *
160
   * @return "org.fxmisc.wellbehaved.event.inputmap"
161
   */
162
  private String getInputMapKey() {
163
    return "org.fxmisc.wellbehaved.event.inputmap";
164
  }
165
166
  public void scrollToTop() {
167
    getEditor().moveTo( 0 );
168
  }
169
170
  private void setEditor( StyleClassedTextArea textArea ) {
171
    this.editor = textArea;
172
  }
173
174
  public synchronized StyleClassedTextArea getEditor() {
175
    if( this.editor == null ) {
176
      setEditor( createTextArea() );
177
    }
178
179
    return this.editor;
180
  }
181
182
  /**
183
   * Returns the scroll pane that contains the text area.
184
   *
185
   * @return The scroll pane that contains the content to edit.
186
   */
187
  public synchronized VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() {
188
    if( this.scrollPane == null ) {
189
      this.scrollPane = createScrollPane();
190
    }
191
192
    return this.scrollPane;
193
  }
194
195
  protected VirtualizedScrollPane<StyleClassedTextArea> createScrollPane() {
196
    final VirtualizedScrollPane<StyleClassedTextArea> pane = new VirtualizedScrollPane<>( getEditor() );
197
    pane.setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS );
198
199
    return pane;
200
  }
201
202
  protected StyleClassedTextArea createTextArea() {
203
    return new StyleClassedTextArea( false );
204
  }
205
206
  public Path getPath() {
207
    return this.path.get();
208
  }
209
210
  public void setPath( final Path path ) {
211
    this.path.set( path );
212
  }
213
214
  public ObjectProperty<Path> pathProperty() {
215
    return this.path;
216
  }
217
}
1218
A src/main/java/com/scrivenvar/editor/HyperlinkModel.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editor;
29
30
import com.vladsch.flexmark.ast.Link;
31
32
/**
33
 * Represents the model for a hyperlink: text and url text.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public class HyperlinkModel {
38
39
  private String text;
40
  private String url;
41
  private String title;
42
43
  /**
44
   * Constructs a new hyperlink model in Markdown format by default with no
45
   * title (i.e., tooltip).
46
   *
47
   * @param text The hyperlink text displayed (e.g., displayed to the user).
48
   * @param url The destination URL (e.g., when clicked).
49
   */
50
  public HyperlinkModel( final String text, final String url ) {
51
    this( text, url, null );
52
  }
53
54
  /**
55
   * Constructs a new hyperlink model for the given AST link.
56
   * 
57
   * @param link A markdown link.
58
   */
59
  public HyperlinkModel( final Link link ) {
60
    this(
61
      link.getText().toString(),
62
      link.getUrl().toString(),
63
      link.getTitle().toString()
64
    );
65
  }
66
67
  /**
68
   * Constructs a new hyperlink model in Markdown format by default.
69
   *
70
   * @param text The hyperlink text displayed (e.g., displayed to the user).
71
   * @param url The destination URL (e.g., when clicked).
72
   * @param title The hyperlink title (e.g., shown as a tooltip).
73
   */
74
  public HyperlinkModel( final String text, final String url, final String title ) {
75
    setText( text );
76
    setUrl( url );
77
    setTitle( title );
78
  }
79
80
  /**
81
   * Returns the string in Markdown format by default.
82
   *
83
   * @return A markdown version of the hyperlink.
84
   */
85
  @Override
86
  public String toString() {
87
    String format = "%s%s%s";
88
89
    if( hasText() ) {
90
      format = "[%s]" + (hasTitle() ? "(%s \"%s\")" : "(%s%s)");
91
    }
92
93
    // Becomes ""+URL+"" if no text is set.
94
    // Becomes [TITLE]+(URL)+"" if no title is set.
95
    // Becomes [TITLE]+(URL+ \"TITLE\") if title is set.
96
    return String.format( format, getText(), getUrl(), getTitle() );
97
  }
98
99
  public final void setText( final String text ) {
100
    this.text = nullSafe( text );
101
  }
102
103
  public final void setUrl( final String url ) {
104
    this.url = nullSafe( url );
105
  }
106
107
  public final void setTitle( final String title ) {
108
    this.title = nullSafe( title );
109
  }
110
111
  /**
112
   * Answers whether text has been set for the hyperlink.
113
   *
114
   * @return true This is a text link.
115
   */
116
  public boolean hasText() {
117
    return !getText().isEmpty();
118
  }
119
120
  /**
121
   * Answers whether a title (tooltip) has been set for the hyperlink.
122
   *
123
   * @return true There is a title.
124
   */
125
  public boolean hasTitle() {
126
    return !getTitle().isEmpty();
127
  }
128
129
  public String getText() {
130
    return this.text;
131
  }
132
133
  public String getUrl() {
134
    return this.url;
135
  }
136
137
  public String getTitle() {
138
    return this.title;
139
  }
140
141
  private String nullSafe( final String s ) {
142
    return s == null ? "" : s;
143
  }
144
}
1145
A src/main/java/com/scrivenvar/editor/LinkVisitor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editor;
29
30
import com.vladsch.flexmark.ast.Link;
31
import com.vladsch.flexmark.ast.Node;
32
import com.vladsch.flexmark.ast.NodeVisitor;
33
import com.vladsch.flexmark.ast.VisitHandler;
34
35
/**
36
 * @author White Magic Software, Ltd.
37
 */
38
public class LinkVisitor {
39
40
  private NodeVisitor visitor;
41
  private Link link;
42
  private final int offset;
43
44
  /**
45
   * Creates a hyperlink given an offset into a paragraph and the markdown AST
46
   * link node.
47
   *
48
   * @param index Index into the paragraph that indicates the hyperlink to
49
   * change.
50
   */
51
  public LinkVisitor( final int index ) {
52
    this.offset = index;
53
  }
54
55
  public Link process( final Node root ) {
56
    getVisitor().visit( root );
57
    return getLink();
58
  }
59
60
  /**
61
   *
62
   * @param link Not null.
63
   */
64
  private void visit( final Link link ) {
65
    final int began = link.getStartOffset();
66
    final int ended = link.getEndOffset();
67
    final int index = getOffset();
68
69
    if( index >= began && index <= ended ) {
70
      setLink( link );
71
    }
72
  }
73
74
  private synchronized NodeVisitor getVisitor() {
75
    if( this.visitor == null ) {
76
      this.visitor = createVisitor();
77
    }
78
79
    return this.visitor;
80
  }
81
82
  protected NodeVisitor createVisitor() {
83
    return new NodeVisitor(
84
      new VisitHandler<>( Link.class, LinkVisitor.this::visit ) );
85
  }
86
87
  private Link getLink() {
88
    return this.link;
89
  }
90
91
  private void setLink( final Link link ) {
92
    this.link = link;
93
  }
94
95
  public int getOffset() {
96
    return this.offset;
97
  }
98
}
199
A src/main/java/com/scrivenvar/editor/MarkdownEditorPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editor;
29
30
import static com.scrivenvar.Constants.STYLESHEET_EDITOR;
31
import com.scrivenvar.dialogs.ImageDialog;
32
import com.scrivenvar.dialogs.LinkDialog;
33
import com.scrivenvar.processors.MarkdownProcessor;
34
import static com.scrivenvar.util.Utils.ltrim;
35
import static com.scrivenvar.util.Utils.rtrim;
36
import com.vladsch.flexmark.ast.Link;
37
import com.vladsch.flexmark.ast.Node;
38
import java.nio.file.Path;
39
import java.util.regex.Matcher;
40
import java.util.regex.Pattern;
41
import javafx.beans.value.ObservableValue;
42
import javafx.scene.control.Dialog;
43
import javafx.scene.control.IndexRange;
44
import static javafx.scene.input.KeyCode.ENTER;
45
import javafx.scene.input.KeyEvent;
46
import javafx.stage.Window;
47
import org.fxmisc.richtext.StyleClassedTextArea;
48
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
49
50
/**
51
 * Markdown editor pane.
52
 *
53
 * @author Karl Tauber and White Magic Software, Ltd.
54
 */
55
public class MarkdownEditorPane extends EditorPane {
56
57
  private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile(
58
    "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" );
59
60
  public MarkdownEditorPane() {
61
    initEditor();
62
  }
63
64
  private void initEditor() {
65
    final StyleClassedTextArea textArea = getEditor();
66
67
    textArea.setWrapText( true );
68
    textArea.getStyleClass().add( "markdown-editor" );
69
    textArea.getStylesheets().add( STYLESHEET_EDITOR );
70
71
    addEventListener( keyPressed( ENTER ), this::enterPressed );
72
73
    // TODO: Wait for implementation that allows cutting lines, not paragraphs.
74
//    addEventListener( keyPressed( X, SHORTCUT_DOWN ), this::cutLine );
75
  }
76
77
  public ObservableValue<String> markdownProperty() {
78
    return getEditor().textProperty();
79
  }
80
81
  private void enterPressed( final KeyEvent e ) {
82
    final StyleClassedTextArea textArea = getEditor();
83
    final String currentLine = textArea.getText( textArea.getCurrentParagraph() );
84
    final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine );
85
86
    String newText = "\n";
87
88
    if( matcher.matches() ) {
89
      if( !matcher.group( 2 ).isEmpty() ) {
90
        // indent new line with same whitespace characters and list markers as current line
91
        newText = newText.concat( matcher.group( 1 ) );
92
      } else {
93
        // current line contains only whitespace characters and list markers
94
        // --> empty current line
95
        final int caretPosition = textArea.getCaretPosition();
96
        textArea.selectRange( caretPosition - currentLine.length(), caretPosition );
97
      }
98
    }
99
100
    textArea.replaceSelection( newText );
101
  }
102
103
  public void surroundSelection( final String leading, final String trailing ) {
104
    surroundSelection( leading, trailing, null );
105
  }
106
107
  public void surroundSelection( String leading, String trailing, final String hint ) {
108
    final StyleClassedTextArea textArea = getEditor();
109
110
    // Note: not using textArea.insertText() to insert leading and trailing
111
    // because this would add two changes to undo history
112
    final IndexRange selection = textArea.getSelection();
113
    int start = selection.getStart();
114
    int end = selection.getEnd();
115
116
    final String selectedText = textArea.getSelectedText();
117
118
    // remove leading and trailing whitespaces from selected text
119
    String trimmedSelectedText = selectedText.trim();
120
    if( trimmedSelectedText.length() < selectedText.length() ) {
121
      start += selectedText.indexOf( trimmedSelectedText );
122
      end = start + trimmedSelectedText.length();
123
    }
124
125
    // remove leading whitespaces from leading text if selection starts at zero
126
    if( start == 0 ) {
127
      leading = ltrim( leading );
128
    }
129
130
    // remove trailing whitespaces from trailing text if selection ends at text end
131
    if( end == textArea.getLength() ) {
132
      trailing = rtrim( trailing );
133
    }
134
135
    // remove leading line separators from leading text
136
    // if there are line separators before the selected text
137
    if( leading.startsWith( "\n" ) ) {
138
      for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) {
139
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
140
          break;
141
        }
142
        leading = leading.substring( 1 );
143
      }
144
    }
145
146
    // remove trailing line separators from trailing or leading text
147
    // if there are line separators after the selected text
148
    final boolean trailingIsEmpty = trailing.isEmpty();
149
    String str = trailingIsEmpty ? leading : trailing;
150
151
    if( str.endsWith( "\n" ) ) {
152
      final int length = textArea.getLength();
153
154
      for( int i = end; i < length && str.endsWith( "\n" ); i++ ) {
155
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
156
          break;
157
        }
158
159
        str = str.substring( 0, str.length() - 1 );
160
      }
161
162
      if( trailingIsEmpty ) {
163
        leading = str;
164
      } else {
165
        trailing = str;
166
      }
167
    }
168
169
    int selStart = start + leading.length();
170
    int selEnd = end + leading.length();
171
172
    // insert hint text if selection is empty
173
    if( hint != null && trimmedSelectedText.isEmpty() ) {
174
      trimmedSelectedText = hint;
175
      selEnd = selStart + hint.length();
176
    }
177
178
    // prevent undo merging with previous text entered by user
179
    getUndoManager().preventMerge();
180
181
    // replace text and update selection
182
    textArea.replaceText( start, end, leading + trimmedSelectedText + trailing );
183
    textArea.selectRange( selStart, selEnd );
184
  }
185
186
  /**
187
   * Returns one of: selected text, word under cursor, or parsed hyperlink from
188
   * the markdown AST.
189
   *
190
   * @return
191
   */
192
  private HyperlinkModel getHyperlink() {
193
    final StyleClassedTextArea textArea = getEditor();
194
    final String selectedText = textArea.getSelectedText();
195
196
    // Get the current paragraph, convert to Markdown nodes.
197
    final MarkdownProcessor mp = new MarkdownProcessor( null );
198
    final int p = textArea.getCurrentParagraph();
199
    final String paragraph = textArea.getText( p );
200
    final Node node = mp.toNode( paragraph );
201
    final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() );
202
    final Link link = visitor.process( node );
203
204
    if( link != null ) {
205
      textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() );
206
    }
207
208
    final HyperlinkModel model = createHyperlinkModel(
209
      link, selectedText, "https://website.com"
210
    );
211
212
    return model;
213
  }
214
215
  private HyperlinkModel createHyperlinkModel(
216
    final Link link, final String selection, final String url ) {
217
218
    return link == null
219
      ? new HyperlinkModel( selection, url )
220
      : new HyperlinkModel( link );
221
  }
222
223
  private Path getParentPath() {
224
    final Path parentPath = getPath();
225
    return (parentPath != null) ? parentPath.getParent() : null;
226
  }
227
228
  private Dialog<String> createLinkDialog() {
229
    return new LinkDialog( getWindow(), getHyperlink(), getParentPath() );
230
  }
231
232
  private Dialog<String> createImageDialog() {
233
    return new ImageDialog( getWindow(), getParentPath() );
234
  }
235
236
  private void insertObject( final Dialog<String> dialog ) {
237
    dialog.showAndWait().ifPresent( result -> {
238
      getEditor().replaceSelection( result );
239
    } );
240
  }
241
242
  public void insertLink() {
243
    insertObject( createLinkDialog() );
244
  }
245
246
  public void insertImage() {
247
    insertObject( createImageDialog() );
248
  }
249
250
  private Window getWindow() {
251
    return getScrollPane().getScene().getWindow();
252
  }
253
}
1254
A src/main/java/com/scrivenvar/editor/VariableNameInjector.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editor;
29
30
import static com.scrivenvar.Constants.SEPARATOR;
31
import com.scrivenvar.FileEditorTabPane;
32
import com.scrivenvar.Services;
33
import com.scrivenvar.decorators.VariableDecorator;
34
import com.scrivenvar.decorators.YamlVariableDecorator;
35
import com.scrivenvar.definition.DefinitionPane;
36
import static com.scrivenvar.definition.Lists.getFirst;
37
import static com.scrivenvar.definition.Lists.getLast;
38
import com.scrivenvar.service.Settings;
39
import com.scrivenvar.ui.VariableTreeItem;
40
import static java.lang.Character.isSpaceChar;
41
import static java.lang.Character.isWhitespace;
42
import static java.lang.Math.min;
43
import java.util.function.Consumer;
44
import javafx.collections.ObservableList;
45
import javafx.event.Event;
46
import javafx.scene.control.IndexRange;
47
import javafx.scene.control.TreeItem;
48
import javafx.scene.input.InputEvent;
49
import javafx.scene.input.KeyCode;
50
import static javafx.scene.input.KeyCode.AT;
51
import static javafx.scene.input.KeyCode.DIGIT2;
52
import static javafx.scene.input.KeyCode.ENTER;
53
import static javafx.scene.input.KeyCode.MINUS;
54
import static javafx.scene.input.KeyCode.SPACE;
55
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
56
import static javafx.scene.input.KeyCombination.SHIFT_DOWN;
57
import javafx.scene.input.KeyEvent;
58
import org.fxmisc.richtext.StyledTextArea;
59
import org.fxmisc.wellbehaved.event.EventPattern;
60
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
61
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
62
import org.fxmisc.wellbehaved.event.InputMap;
63
import static org.fxmisc.wellbehaved.event.InputMap.consume;
64
import static org.fxmisc.wellbehaved.event.InputMap.sequence;
65
import static com.scrivenvar.definition.Lists.getFirst;
66
import static com.scrivenvar.definition.Lists.getLast;
67
import static java.lang.Character.isSpaceChar;
68
import static java.lang.Character.isWhitespace;
69
import static java.lang.Math.min;
70
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
71
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
72
import static org.fxmisc.wellbehaved.event.InputMap.consume;
73
import static com.scrivenvar.definition.Lists.getFirst;
74
import static com.scrivenvar.definition.Lists.getLast;
75
import static java.lang.Character.isSpaceChar;
76
import static java.lang.Character.isWhitespace;
77
import static java.lang.Math.min;
78
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
79
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
80
import static org.fxmisc.wellbehaved.event.InputMap.consume;
81
import static com.scrivenvar.definition.Lists.getFirst;
82
import static com.scrivenvar.definition.Lists.getLast;
83
import static java.lang.Character.isSpaceChar;
84
import static java.lang.Character.isWhitespace;
85
import static java.lang.Math.min;
86
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
87
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
88
import static org.fxmisc.wellbehaved.event.InputMap.consume;
89
import static com.scrivenvar.definition.Lists.getFirst;
90
import static com.scrivenvar.definition.Lists.getLast;
91
import static java.lang.Character.isSpaceChar;
92
import static java.lang.Character.isWhitespace;
93
import static java.lang.Math.min;
94
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
95
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
96
import static org.fxmisc.wellbehaved.event.InputMap.consume;
97
import static com.scrivenvar.definition.Lists.getFirst;
98
import static com.scrivenvar.definition.Lists.getLast;
99
import static java.lang.Character.isSpaceChar;
100
import static java.lang.Character.isWhitespace;
101
import static java.lang.Math.min;
102
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
103
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
104
import static org.fxmisc.wellbehaved.event.InputMap.consume;
105
import static com.scrivenvar.definition.Lists.getFirst;
106
import static com.scrivenvar.definition.Lists.getLast;
107
import static java.lang.Character.isSpaceChar;
108
import static java.lang.Character.isWhitespace;
109
import static java.lang.Math.min;
110
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
111
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
112
import static org.fxmisc.wellbehaved.event.InputMap.consume;
113
import static com.scrivenvar.definition.Lists.getFirst;
114
import static com.scrivenvar.definition.Lists.getLast;
115
import static java.lang.Character.isSpaceChar;
116
import static java.lang.Character.isWhitespace;
117
import static java.lang.Math.min;
118
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
119
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
120
import static org.fxmisc.wellbehaved.event.InputMap.consume;
121
122
/**
123
 * Provides the logic for injecting variable names within the editor.
124
 *
125
 * @author White Magic Software, Ltd.
126
 */
127
public class VariableNameInjector {
128
129
  public static final int DEFAULT_MAX_VAR_LENGTH = 64;
130
131
  private static final int NO_DIFFERENCE = -1;
132
133
  private final Settings settings = Services.load( Settings.class );
134
135
  /**
136
   * Used to capture keyboard events once the user presses @.
137
   */
138
  private InputMap<InputEvent> keyboardMap;
139
140
  private FileEditorTabPane fileEditorPane;
141
  private DefinitionPane definitionPane;
142
143
  /**
144
   * Position of the variable in the text when in variable mode (0 by default).
145
   */
146
  private int initialCaretPosition;
147
148
  public VariableNameInjector(
149
    final FileEditorTabPane editorPane,
150
    final DefinitionPane definitionPane ) {
151
    setFileEditorPane( editorPane );
152
    setDefinitionPane( definitionPane );
153
154
    initKeyboardEventListeners();
155
  }
156
157
  /**
158
   * Traps keys for performing various short-cut tasks, such as @-mode variable
159
   * insertion and control+space for variable autocomplete.
160
   *
161
   * @ key is pressed, a new keyboard map is inserted in place of the current
162
   * map -- this class goes into "variable edit mode" (a.k.a. vMode).
163
   *
164
   * @see createKeyboardMap()
165
   */
166
  private void initKeyboardEventListeners() {
167
    addEventListener( keyPressed( SPACE, CONTROL_DOWN ), this::autocomplete );
168
169
    // @ key in Linux?
170
    addEventListener( keyPressed( DIGIT2, SHIFT_DOWN ), this::vMode );
171
    // @ key in Windows.
172
    addEventListener( keyPressed( AT ), this::vMode );
173
  }
174
175
  /**
176
   * The @ symbol is a short-cut to inserting a YAML variable reference.
177
   *
178
   * @param e Superfluous information about the key that was pressed.
179
   */
180
  private void vMode( KeyEvent e ) {
181
    setInitialCaretPosition();
182
    vModeStart();
183
    vModeAutocomplete();
184
  }
185
186
  /**
187
   * Receives key presses until the user completes the variable selection. This
188
   * allows the arrow keys to be used for selecting variables.
189
   *
190
   * @param e The key that was pressed.
191
   */
192
  private void vModeKeyPressed( KeyEvent e ) {
193
    final KeyCode keyCode = e.getCode();
194
195
    switch( keyCode ) {
196
      case BACK_SPACE:
197
        // Don't decorate the variable upon exiting vMode.
198
        vModeBackspace();
199
        break;
200
201
      case ESCAPE:
202
        // Don't decorate the variable upon exiting vMode.
203
        vModeStop();
204
        break;
205
206
      case ENTER:
207
      case PERIOD:
208
      case RIGHT:
209
      case END:
210
        // Stop at a leaf node, ENTER means accept.
211
        if( vModeConditionalComplete() && keyCode == ENTER ) {
212
          vModeStop();
213
214
          // Decorate the variable upon exiting vMode.
215
          decorateVariable();
216
        }
217
        break;
218
219
      case UP:
220
        cyclePathPrev();
221
        break;
222
223
      case DOWN:
224
        cyclePathNext();
225
        break;
226
227
      default:
228
        vModeFilterKeyPressed( e );
229
        break;
230
    }
231
232
    e.consume();
233
  }
234
235
  private void vModeBackspace() {
236
    deleteSelection();
237
238
    // Break out of variable mode by back spacing to the original position.
239
    if( getCurrentCaretPosition() > getInitialCaretPosition() ) {
240
      vModeAutocomplete();
241
    } else {
242
      vModeStop();
243
    }
244
  }
245
246
  /**
247
   * Updates the text with the path selected (or typed) by the user.
248
   */
249
  private void vModeAutocomplete() {
250
    final TreeItem<String> node = getCurrentNode();
251
252
    if( !node.isLeaf() ) {
253
      final String word = getLastPathWord();
254
      final String label = node.getValue();
255
      final int delta = difference( label, word );
256
      final String remainder = delta == NO_DIFFERENCE
257
        ? label
258
        : label.substring( delta );
259
260
      final StyledTextArea textArea = getEditor();
261
      final int posBegan = getCurrentCaretPosition();
262
      final int posEnded = posBegan + remainder.length();
263
264
      textArea.replaceSelection( remainder );
265
266
      if( posEnded - posBegan > 0 ) {
267
        textArea.selectRange( posEnded, posBegan );
268
      }
269
270
      expand( node );
271
    }
272
  }
273
274
  /**
275
   * Only variable name keys can pass through the filter. This is called when
276
   * the user presses a key.
277
   *
278
   * @param e The key that was pressed.
279
   */
280
  private void vModeFilterKeyPressed( final KeyEvent e ) {
281
    if( isVariableNameKey( e ) ) {
282
      typed( e.getText() );
283
    }
284
  }
285
286
  /**
287
   * Performs an autocomplete depending on whether the user has finished typing
288
   * in a word. If there is a selected range, then this will complete the most
289
   * recent word and jump to the next child.
290
   *
291
   * @return true The auto-completed node was a terminal node.
292
   */
293
  private boolean vModeConditionalComplete() {
294
    acceptPath();
295
296
    final TreeItem<String> node = getCurrentNode();
297
    final boolean terminal = isTerminal( node );
298
299
    if( !terminal ) {
300
      typed( SEPARATOR );
301
    }
302
303
    return terminal;
304
  }
305
306
  /**
307
   * Pressing control+space will find a node that matches the current word and
308
   * substitute the YAML variable reference. This is called when the user is not
309
   * editing in vMode.
310
   *
311
   * @param e Ignored -- it can only be Ctrl+Space.
312
   */
313
  private void autocomplete( KeyEvent e ) {
314
    final String paragraph = getCaretParagraph();
315
    final int[] boundaries = getWordBoundaries( paragraph );
316
    final String word = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] );
317
318
    final VariableTreeItem<String> leaf = findLeaf( word );
319
320
    if( leaf != null ) {
321
      replaceText( boundaries[ 0 ], boundaries[ 1 ], leaf.toPath() );
322
      decorateVariable();
323
      expand( leaf );
324
    }
325
  }
326
327
  /**
328
   * Called when autocomplete finishes on a valid leaf or when the user presses
329
   * Enter to finish manual autocomplete.
330
   */
331
  private void decorateVariable() {
332
    // A little bit of duplication...
333
    final String paragraph = getCaretParagraph();
334
    final int[] boundaries = getWordBoundaries( paragraph );
335
    final String old = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] );
336
337
    final String newVariable = getVariableDecorator().decorate( old );
338
339
    final int posEnded = getCurrentCaretPosition();
340
    final int posBegan = posEnded - old.length();
341
342
    getEditor().replaceText( posBegan, posEnded, newVariable );
343
  }
344
345
  /**
346
   * Updates the text at the given position within the current paragraph.
347
   *
348
   * @param posBegan The starting index in the paragraph text to replace.
349
   * @param posEnded The ending index in the paragraph text to replace.
350
   * @param text Overwrite the paragraph substring with this text.
351
   */
352
  private void replaceText(
353
    final int posBegan, final int posEnded, final String text ) {
354
    final int p = getCurrentParagraph();
355
356
    getEditor().replaceText( p, posBegan, p, posEnded, text );
357
  }
358
359
  /**
360
   * Returns the caret's current paragraph position.
361
   *
362
   * @return A number greater than or equal to 0.
363
   */
364
  private int getCurrentParagraph() {
365
    return getEditor().getCurrentParagraph();
366
  }
367
368
  /**
369
   * Returns current word boundary indexes into the current paragraph, including
370
   * punctuation.
371
   *
372
   * @param p The paragraph wherein to hunt word boundaries.
373
   * @param offset The offset into the paragraph to begin scanning left and
374
   * right.
375
   *
376
   * @return The starting and ending index of the word closest to the caret.
377
   */
378
  private int[] getWordBoundaries( final String p, final int offset ) {
379
    // Remove dashes, but retain hyphens. Retain same number of characters
380
    // to preserve relative indexes.
381
    final String paragraph = p.replace( "---", "   " ).replace( "--", "  " );
382
383
    return getWordAt( paragraph, offset );
384
  }
385
386
  /**
387
   * Helper method to get the word boundaries for the current paragraph.
388
   *
389
   * @param paragraph
390
   *
391
   * @return
392
   */
393
  private int[] getWordBoundaries( final String paragraph ) {
394
    return getWordBoundaries( paragraph, getCurrentCaretColumn() );
395
  }
396
397
  /**
398
   * Given an arbitrary offset into a string, this returns the word at that
399
   * index. The inputs and outputs include:
400
   *
401
   * <ul>
402
   * <li>surrounded by space: <code>hello | world!</code> ("");</li>
403
   * <li>end of word: <code>hello| world!</code> ("hello");</li>
404
   * <li>start of a word: <code>hello |world!</code> ("world!");</li>
405
   * <li>within a word: <code>hello wo|rld!</code> ("world!");</li>
406
   * <li>end of a paragraph: <code>hello world!|</code> ("world!");</li>
407
   * <li>start of a paragraph: <code>|hello world!</code> ("hello!"); or</li>
408
   * <li>after punctuation: <code>hello world!|</code> ("world!").</li>
409
   * </ul>
410
   *
411
   * @param p The string to scan for a word.
412
   * @param offset The offset within s to begin searching for the nearest word
413
   * boundary, must not be out of bounds of s.
414
   *
415
   * @return The word in s at the offset.
416
   *
417
   * @see getWordBegan( String, int )
418
   * @see getWordEnded( String, int )
419
   */
420
  private int[] getWordAt( final String p, final int offset ) {
421
    return new int[]{ getWordBegan( p, offset ), getWordEnded( p, offset ) };
422
  }
423
424
  /**
425
   * Returns the index into s where a word begins.
426
   *
427
   * @param s Never null.
428
   * @param offset Index into s to begin searching backwards for a word
429
   * boundary.
430
   *
431
   * @return The index where a word begins.
432
   */
433
  private int getWordBegan( final String s, int offset ) {
434
    while( offset > 0 && isBoundary( s.charAt( offset - 1 ) ) ) {
435
      offset--;
436
    }
437
438
    return offset;
439
  }
440
441
  /**
442
   * Returns the index into s where a word ends.
443
   *
444
   * @param s Never null.
445
   * @param offset Index into s to begin searching forwards for a word boundary.
446
   *
447
   * @return The index where a word ends.
448
   */
449
  private int getWordEnded( final String s, int offset ) {
450
    final int length = s.length();
451
452
    while( offset < length && isBoundary( s.charAt( offset ) ) ) {
453
      offset++;
454
    }
455
456
    return offset;
457
  }
458
459
  /**
460
   * Returns true if the given character can be reasonably expected to be part
461
   * of a word, including punctuation marks.
462
   *
463
   * @param c The character to compare.
464
   *
465
   * @return false The character is a space character.
466
   */
467
  private boolean isBoundary( final char c ) {
468
    return !isSpaceChar( c );
469
  }
470
471
  /**
472
   * Returns the text for the paragraph that contains the caret.
473
   *
474
   * @return A non-null string, possibly empty.
475
   */
476
  private String getCaretParagraph() {
477
    return getEditor().getText( getCurrentParagraph() );
478
  }
479
480
  /**
481
   * Returns true if the node has children that can be selected (i.e., any
482
   * non-leaves).
483
   *
484
   * @param <T> The type that the TreeItem contains.
485
   * @param node The node to test for terminality.
486
   *
487
   * @return true The node has one branch and its a leaf.
488
   */
489
  private <T> boolean isTerminal( final TreeItem<T> node ) {
490
    final ObservableList<TreeItem<T>> branches = node.getChildren();
491
492
    return branches.size() == 1 && branches.get( 0 ).isLeaf();
493
  }
494
495
  /**
496
   * Inserts text that the user typed at the current caret position, then
497
   * performs an autocomplete for the variable name.
498
   *
499
   * @param text The text to insert, never null.
500
   */
501
  private void typed( final String text ) {
502
    getEditor().replaceSelection( text );
503
    vModeAutocomplete();
504
  }
505
506
  /**
507
   * Called when the user presses either End or Enter key.
508
   */
509
  private void acceptPath() {
510
    final IndexRange range = getSelectionRange();
511
512
    if( range != null ) {
513
      final int rangeEnd = range.getEnd();
514
      final StyledTextArea textArea = getEditor();
515
      textArea.deselect();
516
      textArea.moveTo( rangeEnd );
517
    }
518
  }
519
520
  /**
521
   * Replaces the entirety of the existing path (from the initial caret
522
   * position) with the given path.
523
   *
524
   * @param oldPath The path to replace.
525
   * @param newPath The replacement path.
526
   */
527
  private void replacePath( final String oldPath, final String newPath ) {
528
    final StyledTextArea textArea = getEditor();
529
    final int posBegan = getInitialCaretPosition();
530
    final int posEnded = posBegan + oldPath.length();
531
532
    textArea.deselect();
533
    textArea.replaceText( posBegan, posEnded, newPath );
534
  }
535
536
  /**
537
   * Called when the user presses the Backspace key.
538
   */
539
  private void deleteSelection() {
540
    final StyledTextArea textArea = getEditor();
541
    textArea.replaceSelection( "" );
542
    textArea.deletePreviousChar();
543
  }
544
545
  /**
546
   * Cycles the selected text through the nodes.
547
   *
548
   * @param direction true - next; false - previous
549
   */
550
  private void cycleSelection( final boolean direction ) {
551
    final TreeItem<String> node = getCurrentNode();
552
553
    // Find the sibling for the current selection and replace the current
554
    // selection with the sibling's value
555
    TreeItem< String> cycled = direction
556
      ? node.nextSibling()
557
      : node.previousSibling();
558
559
    // When cycling at the end (or beginning) of the list, jump to the first
560
    // (or last) sibling depending on the cycle direction.
561
    if( cycled == null ) {
562
      cycled = direction ? getFirstSibling( node ) : getLastSibling( node );
563
    }
564
565
    final String path = getCurrentPath();
566
    final String cycledWord = cycled.getValue();
567
    final String word = getLastPathWord();
568
    final int index = path.indexOf( word );
569
    final String cycledPath = path.substring( 0, index ) + cycledWord;
570
571
    expand( cycled );
572
    replacePath( path, cycledPath );
573
  }
574
575
  /**
576
   * Cycles to the next sibling of the currently selected tree node.
577
   */
578
  private void cyclePathNext() {
579
    cycleSelection( true );
580
  }
581
582
  /**
583
   * Cycles to the previous sibling of the currently selected tree node.
584
   */
585
  private void cyclePathPrev() {
586
    cycleSelection( false );
587
  }
588
589
  /**
590
   * Returns the variable name (or as much as has been typed so far). Returns
591
   * all the characters from the initial caret column to the the first
592
   * whitespace character. This will return a path that contains zero or more
593
   * separators.
594
   *
595
   * @return A non-null string, possibly empty.
596
   */
597
  private String getCurrentPath() {
598
    final String s = extractTextChunk();
599
    final int length = s.length();
600
601
    int i = 0;
602
603
    while( i < length && !isWhitespace( s.charAt( i ) ) ) {
604
      i++;
605
    }
606
607
    return s.substring( 0, i );
608
  }
609
610
  private <T> ObservableList<TreeItem<T>> getSiblings(
611
    final TreeItem<T> item ) {
612
    final TreeItem<T> parent = item.getParent();
613
    return parent == null ? item.getChildren() : parent.getChildren();
614
  }
615
616
  private <T> TreeItem<T> getFirstSibling( final TreeItem<T> item ) {
617
    return getFirst( getSiblings( item ), item );
618
  }
619
620
  private <T> TreeItem<T> getLastSibling( final TreeItem<T> item ) {
621
    return getLast( getSiblings( item ), item );
622
  }
623
624
  /**
625
   * Returns the caret position as an offset into the text.
626
   *
627
   * @return A value from 0 to the length of the text (minus one).
628
   */
629
  private int getCurrentCaretPosition() {
630
    return getEditor().getCaretPosition();
631
  }
632
633
  /**
634
   * Returns the caret position within the current paragraph.
635
   *
636
   * @return A value from 0 to the length of the current paragraph.
637
   */
638
  private int getCurrentCaretColumn() {
639
    return getEditor().getCaretColumn();
640
  }
641
642
  /**
643
   * Returns the last word from the path.
644
   *
645
   * @return The last token.
646
   */
647
  private String getLastPathWord() {
648
    String path = getCurrentPath();
649
650
    int i = path.indexOf( SEPARATOR );
651
652
    while( i > 0 ) {
653
      path = path.substring( i + 1 );
654
      i = path.indexOf( SEPARATOR );
655
    }
656
657
    return path;
658
  }
659
660
  /**
661
   * Returns text from the initial caret position until some arbitrarily long
662
   * number of characters. The number of characters extracted will be
663
   * getMaxVarLength, or fewer, depending on how many characters remain to be
664
   * extracted. The result from this method is trimmed to the first whitespace
665
   * character.
666
   *
667
   * @return A chunk of text that includes all the words representing a path,
668
   * and then some.
669
   */
670
  private String extractTextChunk() {
671
    final StyledTextArea textArea = getEditor();
672
    final int textBegan = getInitialCaretPosition();
673
    final int remaining = textArea.getLength() - textBegan;
674
    final int textEnded = min( remaining, getMaxVarLength() );
675
676
    return textArea.getText( textBegan, textEnded );
677
  }
678
679
  /**
680
   * Returns the node for the current path.
681
   */
682
  private TreeItem<String> getCurrentNode() {
683
    return findNode( getCurrentPath() );
684
  }
685
686
  /**
687
   * Finds the node that most closely matches the given path.
688
   *
689
   * @param path The path that represents a node.
690
   *
691
   * @return The node for the path, or the root node if the path could not be
692
   * found, but never null.
693
   */
694
  private TreeItem<String> findNode( final String path ) {
695
    return getDefinitionPane().findNode( path );
696
  }
697
698
  /**
699
   * Finds the first leaf having a value that starts with the given text.
700
   *
701
   * @param text The text to find in the definition tree.
702
   *
703
   * @return The leaf that starts with the given text, or null if not found.
704
   */
705
  private VariableTreeItem<String> findLeaf( final String text ) {
706
    return getDefinitionPane().findLeaf( text );
707
  }
708
709
  /**
710
   * Used to ignore typed keys in favour of trapping pressed keys.
711
   *
712
   * @param e The key that was typed.
713
   */
714
  private void vModeKeyTyped( KeyEvent e ) {
715
    e.consume();
716
  }
717
718
  /**
719
   * Used to lazily initialize the keyboard map.
720
   *
721
   * @return Mappings for keyTyped and keyPressed.
722
   */
723
  protected InputMap<InputEvent> createKeyboardMap() {
724
    return sequence(
725
      consume( keyTyped(), this::vModeKeyTyped ),
726
      consume( keyPressed(), this::vModeKeyPressed )
727
    );
728
  }
729
730
  private InputMap<InputEvent> getKeyboardMap() {
731
    if( this.keyboardMap == null ) {
732
      this.keyboardMap = createKeyboardMap();
733
    }
734
735
    return this.keyboardMap;
736
  }
737
738
  /**
739
   * Collapses the tree then expands and selects the given node.
740
   *
741
   * @param node The node to expand.
742
   */
743
  private void expand( final TreeItem<String> node ) {
744
    final DefinitionPane pane = getDefinitionPane();
745
    pane.collapse();
746
    pane.expand( node );
747
    pane.select( node );
748
  }
749
750
  /**
751
   * Returns true iff the key code the user typed can be used as part of a YAML
752
   * variable name.
753
   *
754
   * @param keyEvent Keyboard key press event information.
755
   *
756
   * @return true The key is a value that can be inserted into the text.
757
   */
758
  private boolean isVariableNameKey( final KeyEvent keyEvent ) {
759
    final KeyCode kc = keyEvent.getCode();
760
761
    return (kc.isLetterKey()
762
      || kc.isDigitKey()
763
      || (keyEvent.isShiftDown() && kc == MINUS))
764
      && !keyEvent.isControlDown();
765
  }
766
767
  /**
768
   * Starts to capture user input events.
769
   */
770
  private void vModeStart() {
771
    addEventListener( getKeyboardMap() );
772
  }
773
774
  /**
775
   * Restores capturing of user input events to the previous event listener.
776
   * Also asks the processing chain to modify the variable text into a
777
   * machine-readable variable based on the format required by the file type.
778
   * For example, a Markdown file (.md) will substitute a $VAR$ name while an R
779
   * file (.Rmd, .Rxml) will use `r#xVAR`.
780
   */
781
  private void vModeStop() {
782
    removeEventListener( getKeyboardMap() );
783
  }
784
785
  private VariableDecorator getVariableDecorator() {
786
    return new YamlVariableDecorator();
787
  }
788
789
  /**
790
   * Returns the index where the two strings diverge.
791
   *
792
   * @param s1 The string that could be a substring of s2, null allowed.
793
   * @param s2 The string that could be a substring of s1, null allowed.
794
   *
795
   * @return NO_DIFFERENCE if the strings are the same, otherwise the index
796
   * where they differ.
797
   */
798
  @SuppressWarnings( "StringEquality" )
799
  private int difference( final CharSequence s1, final CharSequence s2 ) {
800
    if( s1 == s2 ) {
801
      return NO_DIFFERENCE;
802
    }
803
804
    if( s1 == null || s2 == null ) {
805
      return 0;
806
    }
807
808
    int i = 0;
809
    final int limit = min( s1.length(), s2.length() );
810
811
    while( i < limit && s1.charAt( i ) == s2.charAt( i ) ) {
812
      i++;
813
    }
814
815
    // If one string was shorter than the other, that's where they differ.
816
    return i;
817
  }
818
819
  /**
820
   * Delegates to the file editor pane, and, ultimately, to its text area.
821
   */
822
  private <T extends Event, U extends T> void addEventListener(
823
    final EventPattern<? super T, ? extends U> event,
824
    final Consumer<? super U> consumer ) {
825
    getFileEditorPane().addEventListener( event, consumer );
826
  }
827
828
  /**
829
   * Delegates to the file editor pane, and, ultimately, to its text area.
830
   *
831
   * @param map The map of methods to events.
832
   */
833
  private void addEventListener( final InputMap<InputEvent> map ) {
834
    getFileEditorPane().addEventListener( map );
835
  }
836
837
  private void removeEventListener( final InputMap<InputEvent> map ) {
838
    getFileEditorPane().removeEventListener( map );
839
  }
840
841
  /**
842
   * Returns the position of the caret when variable mode editing was requested.
843
   *
844
   * @return The variable mode caret position.
845
   */
846
  private int getInitialCaretPosition() {
847
    return this.initialCaretPosition;
848
  }
849
850
  /**
851
   * Sets the position of the caret when variable mode editing was requested.
852
   * Stores the current position because only the text that comes afterwards is
853
   * a suitable variable reference.
854
   *
855
   * @return The variable mode caret position.
856
   */
857
  private void setInitialCaretPosition() {
858
    this.initialCaretPosition = getEditor().getCaretPosition();
859
  }
860
861
  private StyledTextArea getEditor() {
862
    return getFileEditorPane().getEditor();
863
  }
864
865
  public FileEditorTabPane getFileEditorPane() {
866
    return this.fileEditorPane;
867
  }
868
869
  private void setFileEditorPane( final FileEditorTabPane fileEditorPane ) {
870
    this.fileEditorPane = fileEditorPane;
871
  }
872
873
  private DefinitionPane getDefinitionPane() {
874
    return this.definitionPane;
875
  }
876
877
  private void setDefinitionPane( final DefinitionPane definitionPane ) {
878
    this.definitionPane = definitionPane;
879
  }
880
881
  private IndexRange getSelectionRange() {
882
    return getEditor().getSelection();
883
  }
884
885
  /**
886
   * Don't look ahead too far when trying to find the end of a node.
887
   *
888
   * @return 512 by default.
889
   */
890
  private int getMaxVarLength() {
891
    return getSettings().getSetting(
892
      "editor.variable.maxLength", DEFAULT_MAX_VAR_LENGTH );
893
  }
894
895
  private Settings getSettings() {
896
    return this.settings;
897
  }
898
}
1899
A src/main/java/com/scrivenvar/predicates/files/FileTypePredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.files;
29
30
import java.io.File;
31
import java.nio.file.FileSystems;
32
import java.nio.file.PathMatcher;
33
import java.util.Collection;
34
import java.util.function.Predicate;
35
36
/**
37
 * Responsible for testing whether a given path (to a file) matches one of the
38
 * filename extension patterns provided during construction.
39
 *
40
 * @see http://docs.oracle.com/javase/tutorial/essential/io/find.html
41
 *
42
 * @author White Magic Software, Ltd.
43
 */
44
public class FileTypePredicate implements Predicate<File> {
45
46
  private final PathMatcher matcher;
47
48
  /**
49
   * Constructs a new instance given a set of file extension globs.
50
   *
51
   * @param patterns Comma-separated list of globbed extensions including the
52
   * Kleene star (e.g., <code>*.md,*.markdown,*.txt</code>).
53
   */
54
  public FileTypePredicate( final String patterns ) {
55
    this.matcher = FileSystems.getDefault().getPathMatcher(
56
      "glob:**/{" + patterns + "}"
57
    );
58
  }
59
60
  /**
61
   * Constructs a new instance given a list of file extension globs, each must
62
   * include the Kleene star (a.k.a. asterisk).
63
   *
64
   * @param patterns Collection of globbed extensions.
65
   */
66
  public FileTypePredicate( final Collection<String> patterns ) {
67
    this( String.join( ",", patterns ) );
68
  }
69
70
  /**
71
   * Returns true if the file matches the patterns defined during construction.
72
   *
73
   * @param file The filename to match against the given glob patterns.
74
   *
75
   * @return false The filename does not match the glob patterns.
76
   */
77
  @Override
78
  public boolean test( final File file ) {
79
    return getMatcher().matches( file.toPath() );
80
  }
81
82
  private PathMatcher getMatcher() {
83
    return this.matcher;
84
  }
85
}
186
A src/main/java/com/scrivenvar/predicates/strings/ContainsPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
/**
31
 * Determines if one string contains another.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class ContainsPredicate extends StringPredicate {
36
37
  /**
38
   * Calls the superclass to construct the instance.
39
   *
40
   * @param comparate Not null.
41
   */
42
  public ContainsPredicate( final String comparate ) {
43
    super( comparate );
44
  }
45
46
  /**
47
   * Answers whether the given strings match each other. What match means will
48
   * depend on user preferences. The empty condition is required to return the
49
   * first node in a list of child nodes when the user has not yet selected a
50
   * node.
51
   *
52
   * @param comparator The string to compare against the comparate.
53
   *
54
   * @return true if s1 and s2 are a match according to some criteria,or s2 is
55
   * empty.
56
   */
57
  @Override
58
  public boolean test( final String comparator ) {
59
    final String comparate = getComparate().toLowerCase();
60
    return comparator.contains( comparate.toLowerCase() )
61
      || comparate.isEmpty();
62
  }
63
}
164
A src/main/java/com/scrivenvar/predicates/strings/EqualPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
/**
31
 * Determines if two strings are equal.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class EqualPredicate extends StringPredicate {
36
37
  /**
38
   * Calls the superclass to construct the instance.
39
   *
40
   * @param comparate Not null.
41
   */
42
  public EqualPredicate( final String comparate ) {
43
    super( comparate );
44
  }
45
46
  /**
47
   * Compares two strings.
48
   *
49
   * @param comparator A non-null string, possibly empty.
50
   *
51
   * @return true The strings are equal, ignoring case.
52
   */
53
  @Override
54
  public boolean test( final String comparator ) {
55
    return comparator.equalsIgnoreCase( getComparate() );
56
  }
57
}
158
A src/main/java/com/scrivenvar/predicates/strings/StartsPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
/**
31
 * Determines if a string starts with another.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class StartsPredicate extends StringPredicate {
36
37
  /**
38
   * Calls the superclass to construct the instance.
39
   *
40
   * @param comparate Not null.
41
   */
42
  public StartsPredicate( final String comparate ) {
43
    super( comparate );
44
  }
45
46
  /**
47
   * Compares two strings.
48
   *
49
   * @param comparator A non-null string, possibly empty.
50
   *
51
   * @return true The strings are equal, ignoring case.
52
   */
53
  @Override
54
  public boolean test( final String comparator ) {
55
    final String comparate = getComparate().toLowerCase();
56
    return comparator.startsWith( comparate.toLowerCase() );
57
  }
58
}
159
A src/main/java/com/scrivenvar/predicates/strings/StringPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
import java.util.function.Predicate;
31
32
/**
33
 * General predicate for different types of string comparisons.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public abstract class StringPredicate implements Predicate<String> {
38
39
  private final String comparate;
40
41
  public StringPredicate( final String comparate ) {
42
    this.comparate = comparate;
43
  }
44
45
  protected String getComparate() {
46
    return this.comparate;
47
  }
48
}
149
A src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import static com.scrivenvar.Constants.CARET_POSITION;
31
import java.nio.file.Path;
32
import javafx.beans.value.ObservableValue;
33
import javafx.concurrent.Worker.State;
34
import static javafx.concurrent.Worker.State.SUCCEEDED;
35
import javafx.scene.Node;
36
import javafx.scene.layout.Pane;
37
import javafx.scene.web.WebEngine;
38
import javafx.scene.web.WebView;
39
40
/**
41
 * HTML preview pane is responsible for rendering an HTML document.
42
 *
43
 * @author Karl Tauber and White Magic Software, Ltd.
44
 */
45
public final class HTMLPreviewPane extends Pane {
46
47
  private final WebView webView = new WebView();
48
  private Path path;
49
50
  /**
51
   * Creates a new preview pane that can scroll to the caret position within the
52
   * document.
53
   */
54
  public HTMLPreviewPane() {
55
    initListeners();
56
    initTraversal();
57
  }
58
59
  /**
60
   * Initializes observers for document changes. When the document is reloaded
61
   * with new HTML, this triggers a scroll event that repositions the document
62
   * to the injected caret (that corresponds with the position in the text
63
   * editor).
64
   */
65
  private void initListeners() {
66
    // Scrolls to the caret after the content has been loaded.
67
    getEngine().getLoadWorker().stateProperty().addListener(
68
      (ObservableValue<? extends State> observable,
69
        final State oldValue, final State newValue) -> {
70
        if( newValue == SUCCEEDED ) {
71
          scrollToCaret();
72
        }
73
      } );
74
  }
75
76
  /**
77
   * Ensures images can be found relative to the document.
78
   *
79
   * @return The base path element to use for the document, or the empty string
80
   * if no path has been set, yet.
81
   */
82
  private String getBase() {
83
    final Path basePath = getPath();
84
85
    return basePath == null
86
      ? ""
87
      : ("<base href='" + basePath.getParent().toUri().toString() + "'>");
88
  }
89
90
  /**
91
   * Updates the internal HTML source, loads it into the preview pane, then
92
   * scrolls to the caret position.
93
   *
94
   * @param html The new HTML document to display.
95
   */
96
  public void update( final String html ) {
97
    getEngine().loadContent(
98
      "<!DOCTYPE html>"
99
      + "<html>"
100
      + "<head>"
101
      + "<link rel='stylesheet' href='" + getClass().getResource( "webview.css" ) + "'>"
102
      + getBase()
103
      + "</head>"
104
      + "<body>"
105
      + html
106
      + "</body>"
107
      + "</html>" );
108
  }
109
110
  /**
111
   * Scrolls to the caret position in the document.
112
   */
113
  private void scrollToCaret() {
114
    execute( getScrollScript() );
115
  }
116
117
  /**
118
   * Returns the JavaScript used to scroll the WebView pane.
119
   *
120
   * @return A script that tries to center the view port on the CARET POSITION.
121
   */
122
  private String getScrollScript() {
123
    return ""
124
      + "var e = document.getElementById('" + CARET_POSITION + "');"
125
      + "if( e != null ) { "
126
      + "  Element.prototype.topOffset = function () {"
127
      + "    return this.offsetTop + (this.offsetParent ? this.offsetParent.topOffset() : 0);"
128
      + "  };"
129
      + "  window.scrollTo( 0, e.topOffset() - (window.innerHeight / 2 ) );"
130
      + "}";
131
  }
132
133
  /**
134
   * Prevent tabbing into the preview pane.
135
   */
136
  private void initTraversal() {
137
    getWebView().setFocusTraversable( false );
138
  }
139
140
  private Object execute( final String script ) {
141
    return getEngine().executeScript( script );
142
  }
143
144
  private WebEngine getEngine() {
145
    return getWebView().getEngine();
146
  }
147
148
  private WebView getWebView() {
149
    return this.webView;
150
  }
151
152
  private Path getPath() {
153
    return this.path;
154
  }
155
156
  public void setPath( final Path path ) {
157
    this.path = path;
158
  }
159
  
160
  /**
161
   * Content to embed in a panel.
162
   * 
163
   * @return The content to display to the user.
164
   */
165
  public Node getNode() {
166
    return getWebView();
167
  }
168
}
1169
A src/main/java/com/scrivenvar/processors/AbstractProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
/**
31
 * Responsible for transforming a document through a variety of chained
32
 * handlers. If there are conditions where this handler should not process the
33
 * entire chain, create a second handler, or split the chain into reusable
34
 * sub-chains.
35
 *
36
 * @author White Magic Software, Ltd.
37
 * @param <T> The type of object to process.
38
 */
39
public abstract class AbstractProcessor<T> implements Processor<T> {
40
41
  /**
42
   * Used while processing the entire chain; null to signify no more links.
43
   */
44
  private final Processor<T> next;
45
46
  /**
47
   * Constructs a succession without a successor (i.e., next is null).
48
   */
49
  protected AbstractProcessor() {
50
    this( null );
51
  }
52
53
  /**
54
   * Constructs a new default handler with a given successor.
55
   *
56
   * @param successor Use null to indicate last link in the chain.
57
   */
58
  public AbstractProcessor( final Processor<T> successor ) {
59
    this.next = successor;
60
  }
61
62
  /**
63
   * Processes links in the chain while there are successors and valid data to
64
   * process.
65
   *
66
   * @param t The object to process.
67
   */
68
  @Override
69
  public synchronized void processChain( T t ) {
70
    Processor<T> handler = this;
71
72
    while( handler != null && t != null ) {
73
      t = handler.processLink( t );
74
      handler = handler.next();
75
    }
76
  }
77
78
  @Override
79
  public Processor<T> next() {
80
    return this.next;
81
  }
82
}
183
A src/main/java/com/scrivenvar/processors/HTMLPreviewProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import com.scrivenvar.preview.HTMLPreviewPane;
31
32
/**
33
 * Responsible for notifying the HTMLPreviewPane when the succession chain has
34
 * updated. This decouples knowledge of changes to the editor panel from the
35
 * HTML preview panel as well as any processing that takes place before the
36
 * final HTML preview is rendered. This should be the last link in the processor
37
 * chain.
38
 *
39
 * @author White Magic Software, Ltd.
40
 */
41
public class HTMLPreviewProcessor extends AbstractProcessor<String> {
42
43
  private HTMLPreviewPane htmlPreviewPane;
44
45
  /**
46
   * Constructs the end of a processing chain.
47
   *
48
   * @param htmlPreviewPane The pane to update with the post-processed document.
49
   */
50
  public HTMLPreviewProcessor( final HTMLPreviewPane htmlPreviewPane ) {
51
    super( null );
52
    setHtmlPreviewPane( htmlPreviewPane );
53
  }
54
55
  /**
56
   * Update the preview panel using HTML from the succession chain.
57
   *
58
   * @param html The document content to render in the preview pane. The HTML
59
   * should not contain a doctype, head, or body tag, only content to render
60
   * within the body.
61
   *
62
   * @return null
63
   */
64
  @Override
65
  public String processLink( final String html ) {
66
    getHtmlPreviewPane().update( html );
67
68
    // No more processing required.
69
    return null;
70
  }
71
72
  private HTMLPreviewPane getHtmlPreviewPane() {
73
    return this.htmlPreviewPane;
74
  }
75
76
  private void setHtmlPreviewPane( final HTMLPreviewPane htmlPreviewPane ) {
77
    this.htmlPreviewPane = htmlPreviewPane;
78
  }
79
}
180
A src/main/java/com/scrivenvar/processors/MarkdownCaretInsertionProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import static com.scrivenvar.Constants.MD_CARET_POSITION;
31
import static java.lang.Character.isLetter;
32
33
/**
34
 * Responsible for inserting the magic CARET POSITION into the markdown so
35
 * that, upon rendering into HTML, the HTML pane can scroll to the correct
36
 * position (relative to the caret position in the editor).
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class MarkdownCaretInsertionProcessor extends AbstractProcessor<String> {
41
42
  private final int caretPosition;
43
44
  /**
45
   * Constructs a processor capable of inserting a caret marker into Markdown.
46
   *
47
   * @param processor The next processor in the chain.
48
   * @param position The caret's current position in the text, cannot be null.
49
   */
50
  public MarkdownCaretInsertionProcessor(
51
    final Processor<String> processor, final int position ) {
52
    super( processor );
53
    this.caretPosition = position;
54
  }
55
56
  /**
57
   * Changes the text to insert a "caret" at the caret position. This will
58
   * insert the unique key of Constants.MD_CARET_POSITION into the document.
59
   *
60
   * @param t The document text to process.
61
   *
62
   * @return The document text with the Markdown caret text inserted at the
63
   * caret position (given at construction time).
64
   */
65
  @Override
66
  public String processLink( final String t ) {
67
    int offset = getCaretPosition();
68
    final int length = t.length();
69
70
    // Insert the caret at the closest non-Markdown delimiter (i.e., the 
71
    // closest character from the caret position forward).
72
    while( offset < length && !isLetter( t.charAt( offset ) ) ) {
73
      offset++;
74
    }
75
    
76
    // TODO: Ensure that the caret position is outside of an element, 
77
    // so that a caret inserted in the image doesn't corrupt it. Such as:
78
    //
79
    // ![Screenshot](images/scr|eenshot.png)
80
81
    // Insert the caret position into the Markdown text, but don't interfere
82
    // with the Markdown iteself.
83
    return new StringBuilder( t ).replace(
84
      offset, offset, MD_CARET_POSITION ).toString();
85
  }
86
87
  /**
88
   * Returns the editor's caret position.
89
   *
90
   * @return Where the user has positioned the caret.
91
   */
92
  private int getCaretPosition() {
93
    return this.caretPosition;
94
  }
95
}
196
A src/main/java/com/scrivenvar/processors/MarkdownCaretReplacementProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import static com.scrivenvar.Constants.CARET_POSITION;
31
import static com.scrivenvar.Constants.MD_CARET_POSITION;
32
33
/**
34
 * Responsible for replacing the caret position marker with an HTML element
35
 * suitable to use as a reference for scrolling a view port.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public class MarkdownCaretReplacementProcessor extends AbstractProcessor<String> {
40
  private static final int INDEX_NOT_FOUND = -1;
41
42
  private static final String HTML_ELEMENT
43
    = "<span id='" + CARET_POSITION + "'></span>";
44
45
  public MarkdownCaretReplacementProcessor( final Processor<String> processor ) {
46
    super( processor );
47
  }
48
49
  /**
50
   * Replaces each MD_CARET_POSITION with an HTML element that has an id
51
   * attribute of CARET_POSITION. This should only replace one item.
52
   *
53
   * @param t The text that contains
54
   *
55
   * @return
56
   */
57
  @Override
58
  public String processLink( final String t ) {
59
    return replace( t, MD_CARET_POSITION, HTML_ELEMENT );
60
  }
61
62
  /**
63
   * Replaces the needle with thread in the given haystack. Based on Apache
64
   * Commons 3 StringUtils.replace method. Should be faster than
65
   * String.replace, which performs a little regex under the hood.
66
   *
67
   * @param haystack Search this string for the needle, must not be null.
68
   * @param needle The text to find in the haystack.
69
   * @param thread Replace the needle with this text, if the needle is found.
70
   *
71
   * @return The haystack with the first instance of needle replaced with
72
   * thread.
73
   */
74
  private static String replace(
75
    final String haystack, final String needle, final String thread ) {
76
77
    final int end = haystack.indexOf( needle, 0 );
78
79
    if( end == INDEX_NOT_FOUND ) {
80
      return haystack;
81
    }
82
83
    int start = 0;
84
    final int needleLength = needle.length();
85
86
    int increase = thread.length() - needleLength;
87
    increase = (increase < 0 ? 0 : increase);
88
    final StringBuilder buffer = new StringBuilder( haystack.length() + increase );
89
90
    if( end != INDEX_NOT_FOUND ) {
91
      buffer.append( haystack.substring( start, end ) ).append( thread );
92
      start = end + needleLength;
93
    }
94
95
    return buffer.append( haystack.substring( start ) ).toString();
96
  }
97
}
198
A src/main/java/com/scrivenvar/processors/MarkdownProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import com.vladsch.flexmark.Extension;
31
import com.vladsch.flexmark.ast.Node;
32
import com.vladsch.flexmark.ext.gfm.tables.TablesExtension;
33
import com.vladsch.flexmark.html.HtmlRenderer;
34
import com.vladsch.flexmark.parser.Parser;
35
import java.util.ArrayList;
36
import java.util.List;
37
38
39
/**
40
 * Responsible for parsing a Markdown document and rendering it as HTML.
41
 *
42
 * @author White Magic Software, Ltd.
43
 */
44
public class MarkdownProcessor extends AbstractProcessor<String> {
45
46
  private List<Extension> extensions;
47
48
  /**
49
   * Constructs a new Markdown processor that can create HTML documents.
50
   *
51
   * @param successor Usually the HTML Preview Processor.
52
   */
53
  public MarkdownProcessor( final Processor<String> successor ) {
54
    super( successor );
55
  }
56
57
  /**
58
   * Converts the given Markdown string into HTML, without the doctype, html,
59
   * head, and body tags.
60
   *
61
   * @param markdown The string to convert from Markdown to HTML.
62
   *
63
   * @return The HTML representation of the Markdown document.
64
   */
65
  @Override
66
  public String processLink( final String markdown ) {
67
    return toHtml( markdown );
68
  }
69
70
  /**
71
   * Returns the AST in the form of a node for the given markdown document. This
72
   * can be used, for example, to determine if a hyperlink exists inside of a
73
   * paragraph.
74
   *
75
   * @param markdown The markdown to convert into an AST.
76
   *
77
   * @return The markdown AST for the given text (usually a paragraph).
78
   */
79
  public Node toNode( final String markdown ) {
80
    return parse( markdown );
81
  }
82
83
  /**
84
   * Helper method to create an AST given some markdown.
85
   *
86
   * @param markdown The markdown to parse.
87
   *
88
   * @return The root node of the markdown tree.
89
   */
90
  private Node parse( final String markdown ) {
91
    return createParser().parse( markdown );
92
  }
93
94
  /**
95
   * Converts a string of markdown into HTML.
96
   *
97
   * @param markdown The markdown text to convert to HTML, must not be null.
98
   *
99
   * @return The markdown rendered as an HTML document.
100
   */
101
  private String toHtml( final String markdown ) {
102
    return createRenderer().render( parse( markdown ) );
103
  }
104
105
  /**
106
   * Returns the list of extensions to use when parsing and rendering Markdown
107
   * into HTML.
108
   *
109
   * @return A non-null list of Markdown extensions.
110
   */
111
  private synchronized List<Extension> getExtensions() {
112
    if( this.extensions == null ) {
113
      this.extensions = createExtensions();
114
    }
115
116
    return this.extensions;
117
  }
118
119
  /**
120
   * Creates a list that includes a TablesExtension. Subclasses may override
121
   * this method to insert more extensions, or remove the table extension.
122
   *
123
   * @return A list with an extension for parsing and rendering tables.
124
   */
125
  protected List<Extension> createExtensions() {
126
    final List<Extension> result = new ArrayList<>();
127
    result.add( TablesExtension.create() );
128
    return result;
129
  }
130
131
  /**
132
   * Creates the Markdown document processor.
133
   *
134
   * @return A Parser that can build an abstract syntax tree.
135
   */
136
  private Parser createParser() {
137
    return Parser.builder().extensions( getExtensions() ).build();
138
  }
139
140
  /**
141
   * Creates the HTML document renderer.
142
   *
143
   * @return A renderer that can convert a Markdown AST to HTML.
144
   */
145
  private HtmlRenderer createRenderer() {
146
    return HtmlRenderer.builder().extensions( getExtensions() ).build();
147
  }
148
}
1149
A src/main/java/com/scrivenvar/processors/Processor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
/**
31
 * Responsible for processing documents from one known format to another.
32
 *
33
 * @author White Magic Software, Ltd.
34
 * @param <T> The type of processor to create.
35
 */
36
public interface Processor<T> {
37
  
38
  /**
39
   * Provided so that the chain can be invoked from any link using a given
40
   * value. This should be called automatically by a superclass so that
41
   * the links in the chain need only implement the processLink method.
42
   * 
43
   * @param t The value to pass along to each link in the chain.
44
   * @return The value after having been processed by each link.
45
   */
46
  public void processChain( T t );
47
48
  /**
49
   * Processes the given content providing a transformation from one document
50
   * format into another. For example, this could convert from XML to text using
51
   * an XSLT processor, or from markdown to HTML.
52
   *
53
   * @param t The type of object to process.
54
   *
55
   * @return The post-processed document, or null if processing should stop.
56
   */
57
  public T processLink( T t );
58
59
  /**
60
   * Adds a document processor to call after this processor finishes processing
61
   * the document given to the process method.
62
   *
63
   * @return The processor that should transform the document after this
64
   * instance has finished processing.
65
   */
66
  public Processor<T> next();
67
}
168
A src/main/java/com/scrivenvar/processors/VariableProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import com.scrivenvar.processors.text.TextReplacementFactory;
31
import com.scrivenvar.processors.text.TextReplacer;
32
import java.util.Map;
33
34
/**
35
 * Processes variables in the document and inserts their values into the
36
 * post-processed text.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class VariableProcessor extends AbstractProcessor<String> {
41
42
  private Map<String, String> definitions;
43
44
  /**
45
   * Constructs a new Markdown processor that can create HTML documents.
46
   *
47
   * @param successor Usually the HTML Preview Processor.
48
   */
49
  private VariableProcessor( final Processor<String> successor ) {
50
    super( successor );
51
  }
52
53
  public VariableProcessor(
54
    final Processor<String> successor,
55
    final Map<String, String> map ) {
56
    this( successor );
57
    setDefinitions( map );
58
  }
59
60
  /**
61
   *
62
   * @param text The document text that includes variables that should be
63
   * replaced with values when rendered as HTML.
64
   *
65
   * @return The text with all variables replaced.
66
   */
67
  @Override
68
  public String processLink( final String text ) {
69
    final TextReplacer tr = TextReplacementFactory.getTextReplacer( text.length() );
70
71
    return tr.replace( text, getDefinitions() );
72
  }
73
74
  private Map<String, String> getDefinitions() {
75
    return this.definitions;
76
  }
77
78
  private void setDefinitions( final Map<String, String> definitions ) {
79
    this.definitions = definitions;
80
  }
81
}
182
A src/main/java/com/scrivenvar/processors/text/AbstractTextReplacer.java
1
/*
2
 * The MIT License
3
 *
4
 * Copyright 2016 .
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
package com.scrivenvar.processors.text;
25
26
import java.util.Map;
27
28
/**
29
 * Responsible for common behaviour across all text replacer implementations.
30
 *
31
 * @author White Magic Software, Ltd.
32
 */
33
public abstract class AbstractTextReplacer implements TextReplacer {
34
35
  /**
36
   * Default (empty) constructor.
37
   */
38
  protected AbstractTextReplacer() {
39
  }
40
41
  protected String[] keys( final Map<String, String> map ) {
42
    return map.keySet().toArray( new String[ map.size() ] );
43
  }
44
45
  protected String[] values( final Map<String, String> map ) {
46
    return map.values().toArray( new String[ map.size() ] );
47
  }
48
}
149
A src/main/java/com/scrivenvar/processors/text/AhoCorasickReplacer.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
import java.util.Map;
31
import org.ahocorasick.trie.Emit;
32
import org.ahocorasick.trie.Trie.TrieBuilder;
33
import static org.ahocorasick.trie.Trie.builder;
34
35
/**
36
 * Replaces text using an Aho-Corasick algorithm.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class AhoCorasickReplacer extends AbstractTextReplacer {
41
42
  /**
43
   * Default (empty) constructor.
44
   */
45
  protected AhoCorasickReplacer() {
46
  }
47
48
  @Override
49
  public String replace( final String text, final Map<String, String> map ) {
50
    // Create a buffer sufficiently large that re-allocations are minimized.
51
    final StringBuilder sb = new StringBuilder( (int)(text.length() * 1.25) );
52
53
    // The TrieBuilder should only match whole words and ignore overlaps (there
54
    // shouldn't be any).
55
    final TrieBuilder builder = builder().onlyWholeWords().removeOverlaps();
56
57
    for( final String key : keys( map ) ) {
58
      builder.addKeyword( key );
59
    }
60
61
    int index = 0;
62
63
    for( final Emit emit : builder.build().parseText( text ) ) {
64
      sb.append( text.substring( index, emit.getStart() ) );
65
      sb.append( map.get( emit.getKeyword() ) );
66
      index = emit.getEnd() + 1;
67
    }
68
69
    // Add the remainder of the string (contains no more matches).
70
    sb.append( text.substring( index ) );
71
72
    return sb.toString();
73
  }
74
}
175
A src/main/java/com/scrivenvar/processors/text/StringUtilsReplacer.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
import java.util.Map;
31
import static org.apache.commons.lang.StringUtils.replaceEach;
32
33
/**
34
 * Replaces text using Apache's StringUtils.replaceEach method.
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public class StringUtilsReplacer extends AbstractTextReplacer {
39
40
  /**
41
   * Default (empty) constructor.
42
   */
43
  protected StringUtilsReplacer() {
44
  }
45
46
  @Override
47
  public String replace( final String text, final Map<String, String> map ) {
48
    return replaceEach( text, keys( map ), values( map ) );
49
  }
50
}
151
A src/main/java/com/scrivenvar/processors/text/TextReplacementFactory.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
/**
31
 * Used to generate a class capable of efficiently replacing variable
32
 * definitions with their values.
33
 *
34
 * @author White Magic Software, Ltd.
35
 */
36
public class TextReplacementFactory {
37
38
  /**
39
   * Returns a text search/replacement instance that is reasonably optimal for
40
   * the given length of text.
41
   *
42
   * @param length The length of text that requires some search and replacing.
43
   *
44
   * @return A class that can search and replace text with utmost expediency.
45
   */
46
  public static TextReplacer getTextReplacer( final int length ) {
47
    // After about 1,500 characters, the StringUtils implementation is less
48
    // performant than the Aho-Corsick implementation.
49
    //
50
    // Ssee http://stackoverflow.com/a/40836618/59087
51
    return length < 1500
52
      ? new StringUtilsReplacer()
53
      : new AhoCorasickReplacer();
54
  }
55
}
156
A src/main/java/com/scrivenvar/processors/text/TextReplacer.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
import java.util.Map;
31
32
/**
33
 * Defines the ability to replace text given a set of keys and values.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public interface TextReplacer {
38
39
  /**
40
   * Searches through the given text for any of the keys given in the map and
41
   * replaces the keys that appear in the text with the key's corresponding
42
   * value.
43
   *
44
   * @param text The text that contains zero or more keys.
45
   * @param map The set of keys mapped to replacement values.
46
   *
47
   * @return The given text with all keys replaced with corresponding values.
48
   */
49
  public String replace( String text, Map<String, String> map );
50
}
151
A src/main/java/com/scrivenvar/service/Configuration.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
/**
31
 *
32
 * @author White Magic Software, Ltd.
33
 */
34
public interface Configuration extends Service {
35
36
  public Settings getSettings();
37
38
  public Options getOptions();
39
}
140
A src/main/java/com/scrivenvar/service/Options.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
import java.util.prefs.Preferences;
31
32
/**
33
 * Responsible for persistent options.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public interface Options {
38
39
  public Preferences getState();
40
  
41
  /**
42
   * Stores the key and value into the user preferences to be loaded the next
43
   * time the application is launched.
44
   *
45
   * @param key Name of the key to persist along with its value.
46
   * @param value Value to associate with the key.
47
   */
48
  public void put( String key, String value );
49
50
  /**
51
   * Retrieves the value for a key in the user preferences.
52
   *
53
   * @param key Retrieve the value of this key.
54
   * @param defaultValue The value to return in the event that the given key has
55
   * no associated value.
56
   *
57
   * @return The value associated with the key.
58
   */
59
  public String get( String key, String defaultValue );
60
}
161
A src/main/java/com/scrivenvar/service/Service.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
/**
31
 * All services inherit from this one.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public interface Service {
36
}
137
A src/main/java/com/scrivenvar/service/Settings.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
import java.util.List;
31
32
/**
33
 * Defines how settings and options can be retrieved.
34
 * 
35
 * @author White Magic Software, Ltd.
36
 */
37
public interface Settings extends Service {
38
39
  /**
40
   * Returns a setting property or its default value.
41
   *
42
   * @param property The property key name to obtain its value.
43
   * @param defaultValue The default value to return iff the property cannot
44
   * be found.
45
   *
46
   * @return The property value for the given property key.
47
   */
48
  public String getSetting( String property, String defaultValue );
49
  
50
  /**
51
   * Returns a setting property or its default value.
52
   *
53
   * @param property The property key name to obtain its value.
54
   * @param defaultValue The default value to return iff the property cannot
55
   * be found.
56
   *
57
   * @return The property value for the given property key.
58
   */
59
  public int getSetting( String property, int defaultValue );
60
61
  /**
62
   * Returns a setting property or its default value.
63
   *
64
   * @param property The property key name to obtain its value.
65
   * @param defaults The default values to return iff the property cannot
66
   * be found.
67
   *
68
   * @return The property values for the given property key.
69
   */
70
  public List<Object> getSettingList( String property, List<String> defaults );
71
72
73
  /**
74
   * Convert the generic list of property objects into strings.
75
   *
76
   * @param property The property value to coerce.
77
   * @param defaults The defaults values to use should the property be unset.
78
   *
79
   * @return The list of properties coerced from objects to strings.
80
   */
81
  public List<String> getStringSettingList( String property, List<String> defaults );
82
}
183
A src/main/java/com/scrivenvar/service/events/AlertMessage.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events;
29
30
/**
31
 *
32
 * @author White Magic Software, Ltd.
33
 */
34
public interface AlertMessage {
35
36
  /**
37
   * Dialog box title.
38
   *
39
   * @return A non-null string to use as the title for the dialog.
40
   */
41
  public String getTitle();
42
43
  /**
44
   * Dialog box message content.
45
   *
46
   * @return A non-null string to use as the alert message for the dialog.
47
   */
48
  public String getContent();
49
}
150
A src/main/java/com/scrivenvar/service/events/AlertService.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events;
29
30
import javafx.scene.control.Alert;
31
import javafx.scene.control.ButtonType;
32
import javafx.stage.Window;
33
34
/**
35
 * Provides the application with a uniform way to create alert dialogs.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public interface AlertService {
40
  public static final ButtonType YES = ButtonType.YES;
41
  public static final ButtonType NO = ButtonType.NO;
42
  public static final ButtonType CANCEL = ButtonType.CANCEL;
43
44
  /**
45
   * Called to set the window used as the parent for the alert dialogs.
46
   *
47
   * @param window
48
   */
49
  public void setWindow( Window window );
50
51
  /**
52
   * Constructs a default alert message text for a modal alert dialog.
53
   *
54
   * @param title The dialog box message title.
55
   * @param message The dialog box message content (needs formatting).
56
   * @param args The arguments to the message content that must be formatted.
57
   *
58
   * @return The message suitable for building a modal alert dialog.
59
   */
60
  public AlertMessage createAlertMessage(
61
    String title,
62
    String message,
63
    Object... args );
64
65
  /**
66
   * Creates an alert of alert type error with a message showing the cause of
67
   * the error.
68
   *
69
   * @param alertMessage The error message, title, and possibly more details.
70
   *
71
   * @return A modal alert dialog box ready to display using showAndWait.
72
   */
73
  public Alert createAlertError( AlertMessage alertMessage );
74
75
  /**
76
   * Creates an alert of alert type confirmation with Yes/No/Cancel buttons.
77
   *
78
   * @param alertMessage The message, title, and possibly more details.
79
   *
80
   * @return A modal alert dialog box ready to display using showAndWait.
81
   */
82
  public Alert createAlertConfirmation( AlertMessage alertMessage );
83
}
184
A src/main/java/com/scrivenvar/service/events/impl/ButtonOrderPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events.impl;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.Settings;
32
import javafx.scene.Node;
33
import javafx.scene.control.ButtonBar;
34
import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS;
35
import javafx.scene.control.DialogPane;
36
37
/**
38
 * Ensures a consistent button order for alert dialogs across platforms (because
39
 * the default button order on Linux defies all logic).
40
 *
41
 * @author White Magic Software, Ltd.
42
 */
43
public class ButtonOrderPane extends DialogPane {
44
45
  @Override
46
  protected Node createButtonBar() {
47
    final ButtonBar node = (ButtonBar)super.createButtonBar();
48
    node.setButtonOrder( getButtonOrder() );
49
    return node;
50
  }
51
52
  private String getButtonOrder() {
53
    return getSetting( "dialog.alert.button.order.windows", BUTTON_ORDER_WINDOWS );
54
  }
55
56
  private String getSetting( final String key, final String defaultValue ) {
57
    return getSettings().getSetting( key, defaultValue );
58
  }
59
60
  private Settings getSettings() {
61
    return Services.load( Settings.class );
62
  }
63
}
164
A src/main/java/com/scrivenvar/service/events/impl/DefaultAlertMessage.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events.impl;
29
30
import com.scrivenvar.service.events.AlertMessage;
31
import java.text.MessageFormat;
32
33
/**
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public class DefaultAlertMessage implements AlertMessage {
38
39
  private final String title;
40
  private final String content;
41
42
  /**
43
   * Constructs a default alert message text for an alert modal dialog.
44
   * 
45
   * @param title The dialog box message title.
46
   * @param message The dialog box message content (needs formatting).
47
   * @param args The arguments to the message content that must be formatted.
48
   */
49
  public DefaultAlertMessage(
50
    final String title,
51
    final String message,
52
    final Object... args ) {
53
    this.title = title;
54
    this.content = MessageFormat.format( message, args );
55
  }
56
57
  @Override
58
  public String getTitle() {
59
    return this.title;
60
  }
61
62
  @Override
63
  public String getContent() {
64
    return this.content;
65
  }
66
}
167
A src/main/java/com/scrivenvar/service/events/impl/DefaultAlertService.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events.impl;
29
30
import com.scrivenvar.service.events.AlertMessage;
31
import com.scrivenvar.service.events.AlertService;
32
import javafx.scene.control.Alert;
33
import javafx.scene.control.Alert.AlertType;
34
import static javafx.scene.control.Alert.AlertType.CONFIRMATION;
35
import static javafx.scene.control.Alert.AlertType.ERROR;
36
import javafx.stage.Window;
37
38
/**
39
 * Provides the ability to create error alert boxes.
40
 *
41
 * @author White Magic Software, Ltd.
42
 */
43
public final class DefaultAlertService implements AlertService {
44
45
  private Window window;
46
47
  public DefaultAlertService() {
48
  }
49
50
  public DefaultAlertService( final Window window ) {
51
    this.window = window;
52
  }
53
54
  @Override
55
  public AlertMessage createAlertMessage(
56
    final String title,
57
    final String message,
58
    final Object... args ) {
59
    return new DefaultAlertMessage( title, message, args );
60
  }
61
62
  private Alert createAlertDialog(
63
    final AlertType alertType,
64
    final AlertMessage message ) {
65
66
    final Alert alert = new Alert( alertType );
67
68
    alert.setDialogPane( new ButtonOrderPane() );
69
    alert.setTitle( message.getTitle() );
70
    alert.setHeaderText( null );
71
    alert.setContentText( message.getContent() );
72
    alert.initOwner( getWindow() );
73
74
    return alert;
75
  }
76
77
  @Override
78
  public Alert createAlertConfirmation( final AlertMessage message ) {
79
    final Alert alert = createAlertDialog( CONFIRMATION, message );
80
81
    alert.getButtonTypes().setAll( YES, NO, CANCEL );
82
83
    return alert;
84
  }
85
86
  @Override
87
  public Alert createAlertError( final AlertMessage message ) {
88
    return createAlertDialog( ERROR, message );
89
  }
90
91
  private Window getWindow() {
92
    return this.window;
93
  }
94
95
  @Override
96
  public void setWindow( Window window ) {
97
    this.window = window;
98
  }
99
}
1100
A src/main/java/com/scrivenvar/service/events/impl/FileType.java
1
/*
2
 * To change this license header, choose License Headers in Project Properties.
3
 * To change this template file, choose Tools | Templates
4
 * and open the template in the editor.
5
 */
6
package com.scrivenvar.service.events.impl;
7
8
/**
9
 * Lists known file types for creating document processors via the factory.
10
 *
11
 * @author White Magic Software, Ltd.
12
 */
13
public enum FileType {
14
  MARKDOWN("md", "markdown", "mkdown", "mdown", "mkdn", "mkd", "mdwn", "mdtxt", "mdtext", "text", "txt"),
15
  R_MARKDOWN("Rmd"),
16
  XML("xml");
17
18
  private final String[] extensions;
19
20
  private FileType(final String... extensions) {
21
    this.extensions = extensions;
22
  }
23
24
  /**
25
   * Returns true if the given file type aligns with the extension for this
26
   * enumeration.
27
   *
28
   * @param filetype The file extension to compare against the internal list.
29
   * @return true The given filetype equals (case insensitive) the internal
30
   * type.
31
   */
32
  public boolean isType(final String filetype) {
33
    boolean result = false;
34
35
    for (final String extension : this.extensions) {
36
      if (extension.equalsIgnoreCase(filetype)) {
37
        result = true;
38
        break;
39
      }
40
    }
41
42
    return result;
43
  }
44
}
145
A src/main/java/com/scrivenvar/service/impl/DefaultOptions.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.service.impl;
28
29
import com.scrivenvar.service.Options;
30
import java.util.prefs.Preferences;
31
import static java.util.prefs.Preferences.userRoot;
32
33
/**
34
 * Persistent options user can change at runtime.
35
 *
36
 * @author Karl Tauber and White Magic Software, Ltd.
37
 */
38
public class DefaultOptions implements Options {
39
  private Preferences preferences;
40
  
41
  public DefaultOptions() {
42
    setPreferences( getRootPreferences().node( "options" ) );
43
  }
44
45
  @Override
46
  public void put( final String key, final String value ) {
47
    getPreferences().put( key, value );
48
  }
49
  
50
  @Override
51
  public String get( final String key, final String defalutValue ) {
52
    return getPreferences().get( key, defalutValue );
53
  }
54
  
55
  private void setPreferences( final Preferences preferences ) {
56
    this.preferences = preferences;
57
  }
58
59
  private Preferences getRootPreferences() {
60
    return userRoot().node( "application" );
61
  }
62
63
  @Override
64
  public Preferences getState() {
65
    return getRootPreferences().node( "state" );
66
  }
67
68
  private Preferences getPreferences() {
69
    return this.preferences;
70
  }
71
}
172
A src/main/java/com/scrivenvar/service/impl/DefaultSettings.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.impl;
29
30
import static com.scrivenvar.Constants.SETTINGS_NAME;
31
import com.scrivenvar.service.Settings;
32
import java.io.IOException;
33
import java.net.URISyntaxException;
34
import java.net.URL;
35
import java.util.ArrayList;
36
import java.util.List;
37
import java.util.Objects;
38
import java.util.stream.Collectors;
39
import org.apache.commons.configuration.ConfigurationException;
40
import org.apache.commons.configuration.PropertiesConfiguration;
41
42
/**
43
 * Responsible for loading settings that help avoid hard-coded assumptions.
44
 *
45
 * @author White Magic Software, Ltd.
46
 */
47
public class DefaultSettings implements Settings {
48
49
  private PropertiesConfiguration properties;
50
51
  public DefaultSettings()
52
    throws ConfigurationException, URISyntaxException, IOException {
53
    setProperties(createProperties());
54
  }
55
56
  /**
57
   * Returns the value of a string property.
58
   *
59
   * @param property The property key.
60
   * @param defaultValue The value to return if no property key has been set.
61
   *
62
   * @return The property key value, or defaultValue when no key found.
63
   */
64
  @Override
65
  public String getSetting(final String property, final String defaultValue) {
66
    return getSettings().getString(property, defaultValue);
67
  }
68
69
  /**
70
   * Returns the value of a string property.
71
   *
72
   * @param property The property key.
73
   * @param defaultValue The value to return if no property key has been set.
74
   *
75
   * @return The property key value, or defaultValue when no key found.
76
   */
77
  @Override
78
  public int getSetting(final String property, final int defaultValue) {
79
    return getSettings().getInt(property, defaultValue);
80
  }
81
82
  @Override
83
  public List<Object> getSettingList(final String property, List<String> defaults) {
84
    if (defaults == null) {
85
      defaults = new ArrayList<>();
86
    }
87
    
88
    return getSettings().getList(property, defaults);
89
  }
90
91
  /**
92
   * Convert the generic list of property objects into strings.
93
   *
94
   * @param property The property value to coerce.
95
   * @param defaults The defaults values to use should the property be unset.
96
   *
97
   * @return The list of properties coerced from objects to strings.
98
   */
99
  @Override
100
  public List<String> getStringSettingList(
101
    final String property, final List<String> defaults) {
102
    final List<Object> settings = getSettingList(property, defaults);
103
104
    return settings.stream()
105
      .map(object -> Objects.toString(object, null))
106
      .collect(Collectors.toList());
107
  }
108
109
  private PropertiesConfiguration createProperties()
110
    throws ConfigurationException {
111
    final URL url = getPropertySource();
112
113
    return url == null
114
      ? new PropertiesConfiguration()
115
      : new PropertiesConfiguration(url);
116
  }
117
118
  private URL getPropertySource() {
119
    return getClass().getResource(getSettingsFilename());
120
  }
121
122
  private String getSettingsFilename() {
123
    return SETTINGS_NAME;
124
  }
125
126
  private void setProperties(final PropertiesConfiguration configuration) {
127
    this.properties = configuration;
128
  }
129
130
  private PropertiesConfiguration getSettings() {
131
    return this.properties;
132
  }
133
}
1134
A src/main/java/com/scrivenvar/ui/AbstractPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.ui;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.Options;
32
import java.util.prefs.Preferences;
33
import org.tbee.javafx.scene.layout.fxml.MigPane;
34
35
/**
36
 * Provides options to all subclasses.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public abstract class AbstractPane extends MigPane {
41
42
  private final Options options = Services.load( Options.class );
43
44
  protected Options getOptions() {
45
    return this.options;
46
  }
47
  
48
  protected Preferences getState() {
49
    return getOptions().getState();
50
  }
51
}
152
A src/main/java/com/scrivenvar/ui/VariableTreeItem.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.ui;
29
30
import static com.scrivenvar.Constants.SEPARATOR;
31
import com.scrivenvar.decorators.YamlVariableDecorator;
32
import com.scrivenvar.decorators.VariableDecorator;
33
import static com.scrivenvar.editor.VariableNameInjector.DEFAULT_MAX_VAR_LENGTH;
34
import java.util.HashMap;
35
import java.util.Map;
36
import java.util.Stack;
37
import javafx.scene.control.TreeItem;
38
39
/**
40
 * Provides behaviour afforded to variable names and their corresponding value.
41
 *
42
 * @author White Magic Software, Ltd.
43
 * @param <T> The type of TreeItem (usually String).
44
 */
45
public class VariableTreeItem<T> extends TreeItem<T> {
46
47
  private final static int DEFAULT_MAP_SIZE = 1000;
48
  
49
  private final static VariableDecorator VARIABLE_DECORATOR =
50
    new YamlVariableDecorator();
51
52
  /**
53
   * Flattened tree.
54
   */
55
  private Map<String, String> map;
56
57
  /**
58
   * Constructs a new item with a default value.
59
   *
60
   * @param value Passed up to superclass.
61
   */
62
  public VariableTreeItem( final T value ) {
63
    super( value );
64
  }
65
66
  /**
67
   * Finds a leaf starting at the current node with text that matches the given
68
   * value.
69
   *
70
   * @param text The text to match against each leaf in the tree.
71
   *
72
   * @return The leaf that has a value starting with the given text.
73
   */
74
  public VariableTreeItem<T> findLeaf( final String text ) {
75
    final Stack<VariableTreeItem<T>> stack = new Stack<>();
76
    final VariableTreeItem<T> root = this;
77
78
    stack.push( root );
79
80
    boolean found = false;
81
    VariableTreeItem<T> node = null;
82
83
    while( !found && !stack.isEmpty() ) {
84
      node = stack.pop();
85
86
      if( node.valueStartsWith( text ) ) {
87
        found = true;
88
      } else {
89
        for( final TreeItem<T> child : node.getChildren() ) {
90
          stack.push( (VariableTreeItem<T>)child );
91
        }
92
93
        // No match found, yet.
94
        node = null;
95
      }
96
    }
97
98
    return (VariableTreeItem<T>)node;
99
  }
100
101
  /**
102
   * Returns true if this node is a leaf and its value starts with the given
103
   * text.
104
   *
105
   * @param s The text to compare against the node value.
106
   *
107
   * @return true Node is a leaf and its value starts with the given value.
108
   */
109
  private boolean valueStartsWith( final String s ) {
110
    return isLeaf() && getValue().toString().startsWith( s );
111
  }
112
113
  /**
114
   * Returns the path for this node, with nodes made distinct using the
115
   * separator character. This uses two loops: one for pushing nodes onto a
116
   * stack and one for popping them off to create the path in desired order.
117
   *
118
   * @return A non-null string, possibly empty.
119
   */
120
  public String toPath() {
121
    final Stack<TreeItem<T>> stack = new Stack<>();
122
    TreeItem<T> node = this;
123
124
    while( node.getParent() != null ) {
125
      stack.push( node );
126
      node = node.getParent();
127
    }
128
129
    final StringBuilder sb = new StringBuilder( DEFAULT_MAX_VAR_LENGTH );
130
131
    while( !stack.isEmpty() ) {
132
      node = stack.pop();
133
134
      if( !node.isLeaf() ) {
135
        sb.append( node.getValue() );
136
137
        // This will add a superfluous separator, but instead of peeking at
138
        // the stack all the time, the last separator will be removed outside
139
        // the loop (one operation executed once).
140
        sb.append( SEPARATOR );
141
      }
142
    }
143
144
    // Remove the trailing SEPARATOR.
145
    if( sb.length() > 0 ) {
146
      sb.setLength( sb.length() - 1 );
147
    }
148
149
    return sb.toString();
150
  }
151
152
  /**
153
   * Returns the hierarchy, flattened to key-value pairs.
154
   *
155
   * @return A map of this tree's key-value pairs.
156
   */
157
  public Map<String, String> getMap() {
158
    if( this.map == null ) {
159
      this.map = new HashMap<>( DEFAULT_MAP_SIZE );
160
      populate( this, this.map );
161
    }
162
163
    return this.map;
164
  }
165
166
  private void populate( final TreeItem<T> parent, final Map<String, String> map ) {
167
    for( final TreeItem<T> child : parent.getChildren() ) {
168
      if( child.isLeaf() ) {
169
        @SuppressWarnings( "unchecked" )
170
        final String key = toVariable( ((VariableTreeItem<String>)child).toPath() );
171
        final String value = child.getValue().toString();
172
173
        map.put( key, value );
174
      } else {
175
        populate( child, map );
176
      }
177
    }
178
  }
179
180
  /**
181
   * Converts the name of the key to a simple variable by enclosing it with
182
   * dollar symbols.
183
   *
184
   * @param key The key name to change to a variable.
185
   *
186
   * @return $key$
187
   */
188
  public String toVariable( final String key ) {
189
    return VARIABLE_DECORATOR.decorate( key );
190
  }
191
}
1192
A src/main/java/com/scrivenvar/util/Action.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.util;
29
30
import javafx.beans.value.ObservableBooleanValue;
31
import javafx.event.ActionEvent;
32
import javafx.event.EventHandler;
33
import javafx.scene.input.KeyCombination;
34
import de.jensd.fx.glyphs.GlyphIcons;
35
36
/**
37
 * Simple action class
38
 *
39
 * @author Karl Tauber
40
 */
41
public class Action
42
{
43
	public final String text;
44
	public final KeyCombination accelerator;
45
	public final GlyphIcons icon;
46
	public final EventHandler<ActionEvent> action;
47
	public final ObservableBooleanValue disable;
48
49
	public Action(String text, String accelerator, GlyphIcons icon,
50
		EventHandler<ActionEvent> action)
51
	{
52
		this(text, accelerator, icon, action, null);
53
	}
54
55
	public Action(String text, String accelerator, GlyphIcons icon,
56
		EventHandler<ActionEvent> action, ObservableBooleanValue disable)
57
	{
58
		this.text = text;
59
		this.accelerator = (accelerator != null) ? KeyCombination.valueOf(accelerator) : null;
60
		this.icon = icon;
61
		this.action = action;
62
		this.disable = disable;
63
	}
64
}
165
A src/main/java/com/scrivenvar/util/ActionUtils.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory;
30
import javafx.scene.Node;
31
import javafx.scene.control.Button;
32
import javafx.scene.control.Menu;
33
import javafx.scene.control.MenuItem;
34
import javafx.scene.control.Separator;
35
import javafx.scene.control.SeparatorMenuItem;
36
import javafx.scene.control.ToolBar;
37
import javafx.scene.control.Tooltip;
38
39
/**
40
 * Action utilities
41
 *
42
 * @author Karl Tauber
43
 */
44
public class ActionUtils {
45
46
  public static Menu createMenu( final String text, final Action... actions ) {
47
    return new Menu( text, null, createMenuItems( actions ) );
48
  }
49
50
  public static MenuItem[] createMenuItems( Action... actions ) {
51
    MenuItem[] menuItems = new MenuItem[ actions.length ];
52
    for( int i = 0; i < actions.length; i++ ) {
53
      menuItems[ i ] = (actions[ i ] != null)
54
        ? createMenuItem( actions[ i ] )
55
        : new SeparatorMenuItem();
56
    }
57
    return menuItems;
58
  }
59
60
  public static MenuItem createMenuItem( Action action ) {
61
    MenuItem menuItem = new MenuItem( action.text );
62
    if( action.accelerator != null ) {
63
      menuItem.setAccelerator( action.accelerator );
64
    }
65
66
    if( action.icon != null ) {
67
      menuItem.setGraphic( FontAwesomeIconFactory.get().createIcon( action.icon ) );
68
    }
69
70
    menuItem.setOnAction( action.action );
71
72
    if( action.disable != null ) {
73
      menuItem.disableProperty().bind( action.disable );
74
    }
75
76
    menuItem.setMnemonicParsing( true );
77
78
    return menuItem;
79
  }
80
81
  public static ToolBar createToolBar( Action... actions ) {
82
    return new ToolBar( createToolBarButtons( actions ) );
83
  }
84
85
  public static Node[] createToolBarButtons( Action... actions ) {
86
    Node[] buttons = new Node[ actions.length ];
87
    for( int i = 0; i < actions.length; i++ ) {
88
      buttons[ i ] = (actions[ i ] != null)
89
        ? createToolBarButton( actions[ i ] )
90
        : new Separator();
91
    }
92
    return buttons;
93
  }
94
95
  public static Button createToolBarButton( Action action ) {
96
    Button button = new Button();
97
    button.setGraphic( FontAwesomeIconFactory.get().createIcon( action.icon, "1.2em" ) );
98
    String tooltip = action.text;
99
    if( tooltip.endsWith( "..." ) ) {
100
      tooltip = tooltip.substring( 0, tooltip.length() - 3 );
101
    }
102
    if( action.accelerator != null ) {
103
      tooltip += " (" + action.accelerator.getDisplayText() + ')';
104
    }
105
    button.setTooltip( new Tooltip( tooltip ) );
106
    button.setFocusTraversable( false );
107
    button.setOnAction( action.action );
108
    if( action.disable != null ) {
109
      button.disableProperty().bind( action.disable );
110
    }
111
    return button;
112
  }
113
}
1114
A src/main/java/com/scrivenvar/util/Item.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
/**
30
 * Simple item for a ChoiceBox, ComboBox or ListView. Consists of a string name
31
 * and a value object. toString() returns the name. equals() compares the value
32
 * and hashCode() returns the hash code of the value.
33
 *
34
 * @author Karl Tauber
35
 * @param <V> The type of item value.
36
 */
37
public class Item<V> {
38
39
  public final String name;
40
  public final V value;
41
42
  public Item( final String name, final V value ) {
43
    this.name = name;
44
    this.value = value;
45
  }
46
47
  @Override
48
  public boolean equals( final Object obj ) {
49
    if( this == obj ) {
50
      return true;
51
    }
52
    if( !(obj instanceof Item) ) {
53
      return false;
54
    }
55
    return Utils.safeEquals( value, ((Item<?>)obj).value );
56
  }
57
58
  @Override
59
  public int hashCode() {
60
    return (value != null) ? value.hashCode() : 0;
61
  }
62
63
  @Override
64
  public String toString() {
65
    return name;
66
  }
67
}
168
A src/main/java/com/scrivenvar/util/StageState.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
import java.util.prefs.Preferences;
30
import javafx.application.Platform;
31
import javafx.scene.shape.Rectangle;
32
import javafx.stage.Stage;
33
import javafx.stage.WindowEvent;
34
35
/**
36
 * Saves and restores Stage state (window bounds, maximized, fullScreen).
37
 *
38
 * @author Karl Tauber
39
 */
40
public class StageState {
41
42
  public static final String K_PANE_SPLIT_DEFINITION = "pane.split.definition";
43
  public static final String K_PANE_SPLIT_EDITOR = "pane.split.editor";
44
  public static final String K_PANE_SPLIT_PREVIEW = "pane.split.preview";
45
46
  private final Stage stage;
47
  private final Preferences state;
48
49
  private Rectangle normalBounds;
50
  private boolean runLaterPending;
51
52
  public StageState( Stage stage, Preferences state ) {
53
    this.stage = stage;
54
    this.state = state;
55
56
    restore();
57
58
    stage.addEventHandler( WindowEvent.WINDOW_HIDING, e -> save() );
59
60
    stage.xProperty().addListener( (ob, o, n) -> boundsChanged() );
61
    stage.yProperty().addListener( (ob, o, n) -> boundsChanged() );
62
    stage.widthProperty().addListener( (ob, o, n) -> boundsChanged() );
63
    stage.heightProperty().addListener( (ob, o, n) -> boundsChanged() );
64
  }
65
66
  private void save() {
67
    Rectangle bounds = isNormalState() ? getStageBounds() : normalBounds;
68
    if( bounds != null ) {
69
      state.putDouble( "windowX", bounds.getX() );
70
      state.putDouble( "windowY", bounds.getY() );
71
      state.putDouble( "windowWidth", bounds.getWidth() );
72
      state.putDouble( "windowHeight", bounds.getHeight() );
73
    }
74
    state.putBoolean( "windowMaximized", stage.isMaximized() );
75
    state.putBoolean( "windowFullScreen", stage.isFullScreen() );
76
  }
77
78
  private void restore() {
79
    double x = state.getDouble( "windowX", Double.NaN );
80
    double y = state.getDouble( "windowY", Double.NaN );
81
    double w = state.getDouble( "windowWidth", Double.NaN );
82
    double h = state.getDouble( "windowHeight", Double.NaN );
83
    boolean maximized = state.getBoolean( "windowMaximized", false );
84
    boolean fullScreen = state.getBoolean( "windowFullScreen", false );
85
86
    if( !Double.isNaN( x ) && !Double.isNaN( y ) ) {
87
      stage.setX( x );
88
      stage.setY( y );
89
    } // else: default behavior is center on screen
90
91
    if( !Double.isNaN( w ) && !Double.isNaN( h ) ) {
92
      stage.setWidth( w );
93
      stage.setHeight( h );
94
    } // else: default behavior is use scene size
95
96
    if( fullScreen != stage.isFullScreen() ) {
97
      stage.setFullScreen( fullScreen );
98
    }
99
    if( maximized != stage.isMaximized() ) {
100
      stage.setMaximized( maximized );
101
    }
102
  }
103
104
  /**
105
   * Remembers the window bounds when the window is not iconified, maximized or
106
   * in fullScreen.
107
   */
108
  private void boundsChanged() {
109
    // avoid too many (and useless) runLater() invocations
110
    if( runLaterPending ) {
111
      return;
112
    }
113
    runLaterPending = true;
114
115
    // must use runLater() to ensure that change of all properties
116
    // (x, y, width, height, iconified, maximized and fullScreen)
117
    // has finished
118
    Platform.runLater( () -> {
119
      runLaterPending = false;
120
121
      if( isNormalState() ) {
122
        normalBounds = getStageBounds();
123
      }
124
    } );
125
  }
126
127
  private boolean isNormalState() {
128
    return !stage.isIconified() && !stage.isMaximized() && !stage.isFullScreen();
129
  }
130
131
  private Rectangle getStageBounds() {
132
    return new Rectangle( stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight() );
133
  }
134
}
1135
A src/main/java/com/scrivenvar/util/Utils.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
import java.util.ArrayList;
30
import java.util.prefs.Preferences;
31
32
/**
33
 * @author Karl Tauber
34
 */
35
public class Utils {
36
37
  public static boolean safeEquals( final Object o1, final Object o2 ) {
38
    if( o1 == o2 ) {
39
      return true;
40
    }
41
    if( o1 == null || o2 == null ) {
42
      return false;
43
    }
44
    return o1.equals( o2 );
45
  }
46
47
  public static boolean isNullOrEmpty( final String s ) {
48
    return s == null || s.isEmpty();
49
  }
50
51
  public static String ltrim( final String s ) {
52
    int i = 0;
53
54
    while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) ) {
55
      i++;
56
    }
57
58
    return s.substring( i );
59
  }
60
61
  public static String rtrim( final String s ) {
62
    int i = s.length() - 1;
63
64
    while( i >= 0 && Character.isWhitespace( s.charAt( i ) ) ) {
65
      i--;
66
    }
67
68
    return s.substring( 0, i + 1 );
69
  }
70
71
  public static void putPrefs( Preferences prefs, String key, String value, String def ) {
72
    if( value != def && !value.equals( def ) ) {
73
      prefs.put( key, value );
74
    } else {
75
      prefs.remove( key );
76
    }
77
  }
78
79
  public static void putPrefsInt( Preferences prefs, String key, int value, int def ) {
80
    if( value != def ) {
81
      prefs.putInt( key, value );
82
    } else {
83
      prefs.remove( key );
84
    }
85
  }
86
87
  public static void putPrefsBoolean( Preferences prefs, String key, boolean value, boolean def ) {
88
    if( value != def ) {
89
      prefs.putBoolean( key, value );
90
    } else {
91
      prefs.remove( key );
92
    }
93
  }
94
95
  public static String[] getPrefsStrings( final Preferences prefs, String key ) {
96
    final ArrayList<String> arr = new ArrayList<>( 256 );
97
98
    for( int i = 0; i < 10000; i++ ) {
99
      final String s = prefs.get( key + (i + 1), null );
100
101
      if( s == null ) {
102
        break;
103
      }
104
105
      arr.add( s );
106
    }
107
108
    return arr.toArray( new String[ arr.size() ] );
109
  }
110
111
  public static void putPrefsStrings( Preferences prefs, String key, String[] strings ) {
112
    for( int i = 0; i < strings.length; i++ ) {
113
      prefs.put( key + (i + 1), strings[ i ] );
114
    }
115
116
    for( int i = strings.length; prefs.get( key + (i + 1), null ) != null; i++ ) {
117
      prefs.remove( key + (i + 1) );
118
    }
119
  }
120
}
1121
A src/main/java/com/scrivenvar/yaml/YamlParser.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.yaml;
29
30
import com.fasterxml.jackson.core.JsonGenerationException;
31
import com.fasterxml.jackson.core.ObjectCodec;
32
import com.fasterxml.jackson.core.io.IOContext;
33
import com.fasterxml.jackson.databind.JsonNode;
34
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.fasterxml.jackson.databind.node.ObjectNode;
36
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
37
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
38
import static com.scrivenvar.Constants.SEPARATOR;
39
import com.scrivenvar.decorators.VariableDecorator;
40
import com.scrivenvar.decorators.YamlVariableDecorator;
41
import java.io.IOException;
42
import java.io.InputStream;
43
import java.io.Writer;
44
import java.security.InvalidParameterException;
45
import java.text.MessageFormat;
46
import java.util.HashMap;
47
import java.util.Map;
48
import java.util.Map.Entry;
49
import java.util.regex.Matcher;
50
import java.util.regex.Pattern;
51
import org.yaml.snakeyaml.DumperOptions;
52
53
/**
54
 * <p>
55
 * This program loads a YAML document into memory, scans for variable
56
 * declarations, then substitutes any self-referential values back into the
57
 * document. Its output is the given YAML document without any variables.
58
 * Variables in the YAML document are denoted using a bracketed dollar symbol
59
 * syntax. For example: $field.name$. Some nomenclature to keep from going
60
 * squirrely, consider:
61
 * </p>
62
 *
63
 * <pre>
64
 *   root:
65
 *     node:
66
 *       name: $field.name$
67
 *   field:
68
 *     name: Alan Turing
69
 * </pre>
70
 *
71
 * The various components of the given YAML are called:
72
 *
73
 * <ul>
74
 * <li><code>$field.name$</code> - delimited reference</li>
75
 * <li><code>field.name</code> - reference</li>
76
 * <li><code>name</code> - YAML field</li>
77
 * <li><code>Alan Turing</code> - (dereferenced) field value</li>
78
 * </ul>
79
 *
80
 * @author White Magic Software, Ltd.
81
 */
82
public class YamlParser {
83
84
  private final static int GROUP_DELIMITED = 1;
85
  private final static int GROUP_REFERENCE = 2;
86
87
  private final static VariableDecorator VARIABLE_DECORATOR
88
    = new YamlVariableDecorator();
89
90
  /**
91
   * Compiled version of DEFAULT_REGEX.
92
   */
93
  private final static Pattern REGEX_PATTERN
94
    = Pattern.compile( YamlVariableDecorator.REGEX );
95
96
  /**
97
   * Should be JsonPointer.SEPARATOR, but Jackson YAML uses magic values.
98
   */
99
  private final static char SEPARATOR_YAML = '/';
100
101
  /**
102
   * Start of the Universe (the YAML document node that contains all others).
103
   */
104
  private ObjectNode documentRoot;
105
106
  /**
107
   * Map of references to dereferenced field values.
108
   */
109
  private Map<String, String> references;
110
111
  public YamlParser() {
112
  }
113
114
  /**
115
   * Returns the given string with all the delimited references swapped with
116
   * their recursively resolved values.
117
   *
118
   * @param text The text to parse with zero or more delimited references to
119
   * replace.
120
   *
121
   * @return The substituted value.
122
   *
123
   * @throws InvalidParameterException The text has no associated value.
124
   */
125
  public String substitute( String text ) {
126
    final Matcher matcher = patternMatch( text );
127
    final Map<String, String> map = getReferences();
128
129
    while( matcher.find() ) {
130
      final String key = matcher.group( GROUP_DELIMITED );
131
      final String value = map.get( key );
132
133
      if( value == null ) {
134
        missing( text );
135
      } else {
136
        text = text.replace( key, value );
137
      }
138
    }
139
140
    return text;
141
  }
142
143
  /**
144
   * Returns all the strings with their values resolved in a flat hierarchy.
145
   * This copies all the keys and resolved values into a new map.
146
   *
147
   * @return The new map created with all values having been resolved,
148
   * recursively.
149
   *
150
   * @throws InvalidParameterException A key in the map has no associated value.
151
   */
152
  public Map<String, String> createResolvedMap() {
153
    final Map<String, String> map = new HashMap<>( 1024 );
154
155
    resolve( getDocumentRoot(), "", map );
156
157
    return map;
158
  }
159
160
  /**
161
   * Iterate over a given root node (at any level of the tree) and adapt each
162
   * leaf node.
163
   *
164
   * @param rootNode A JSON node (YAML node) to adapt.
165
   */
166
  private void resolve(
167
    final JsonNode rootNode, final String path, final Map<String, String> map ) {
168
169
    rootNode.fields().forEachRemaining(
170
      (Entry<String, JsonNode> leaf) -> resolve( leaf, path, map )
171
    );
172
  }
173
174
  /**
175
   * Recursively adapt each rootNode to a corresponding rootItem.
176
   *
177
   * @param rootNode The node to adapt.
178
   */
179
  private void resolve(
180
    final Entry<String, JsonNode> rootNode, final String path, final Map<String, String> map ) {
181
    final JsonNode leafNode = rootNode.getValue();
182
    final String key = rootNode.getKey();
183
184
    if( leafNode.isValueNode() ) {
185
      final String value = rootNode.getValue().asText();
186
187
      map.put( VARIABLE_DECORATOR.decorate( path + key ), substitute( value ) );
188
    }
189
190
    if( leafNode.isObject() ) {
191
      resolve( leafNode, path + key + SEPARATOR, map );
192
    }
193
  }
194
195
  /**
196
   * Reads the first document from the given stream of YAML data and returns a
197
   * corresponding object that represents the YAML hierarchy. The calling class
198
   * is responsible for closing the stream. Calling classes should use
199
   * <code>JsonNode.fields()</code> to walk through the YAML tree of fields.
200
   *
201
   * @param in The input stream containing YAML content.
202
   *
203
   * @return An object hierarchy to represent the content.
204
   *
205
   * @throws IOException Could not read the stream.
206
   */
207
  public JsonNode process( final InputStream in ) throws IOException {
208
209
    final ObjectNode root = (ObjectNode)getObjectMapper().readTree( in );
210
    setDocumentRoot( root );
211
    process( root );
212
    return getDocumentRoot();
213
  }
214
215
  /**
216
   * Iterate over a given root node (at any level of the tree) and process each
217
   * leaf node.
218
   *
219
   * @param root A node to process.
220
   */
221
  private void process( final JsonNode root ) {
222
    root.fields().forEachRemaining( this::process );
223
  }
224
225
  /**
226
   * Process the given field, which is a named node. This is where the
227
   * application does the up-front work of mapping references to their fully
228
   * recursively dereferenced values.
229
   *
230
   * @param field The named node.
231
   */
232
  private void process( final Entry<String, JsonNode> field ) {
233
    final JsonNode node = field.getValue();
234
235
    if( node.isObject() ) {
236
      process( node );
237
    } else {
238
      final JsonNode fieldValue = field.getValue();
239
240
      // Only basic data types can be parsed into variable values. For
241
      // node structures, YAML has a built-in mechanism.
242
      if( fieldValue.isValueNode() ) {
243
        try {
244
          resolve( fieldValue.asText() );
245
        } catch( StackOverflowError e ) {
246
          throw new IllegalArgumentException(
247
            "Unresolvable: " + node.textValue() + " = " + fieldValue );
248
        }
249
      }
250
    }
251
  }
252
253
  /**
254
   * Inserts the delimited references and field values into the cache. This will
255
   * overwrite existing references.
256
   *
257
   * @param fieldValue YAML field containing zero or more delimited references.
258
   * If it contains a delimited reference, the parameter is modified with the
259
   * dereferenced value before it is returned.
260
   *
261
   * @return fieldValue without delimited references.
262
   */
263
  private String resolve( String fieldValue ) {
264
    final Matcher matcher = patternMatch( fieldValue );
265
266
    while( matcher.find() ) {
267
      final String delimited = matcher.group( GROUP_DELIMITED );
268
      final String reference = matcher.group( GROUP_REFERENCE );
269
      final String dereference = resolve( lookup( reference ) );
270
271
      fieldValue = fieldValue.replace( delimited, dereference );
272
273
      // This will perform some superfluous calls by overwriting existing
274
      // items in the delimited reference map.
275
      put( delimited, dereference );
276
    }
277
278
    return fieldValue;
279
  }
280
281
  /**
282
   * Inserts a key/value pair into the references map. The map retains
283
   * references and dereferenced values found in the YAML. If the reference
284
   * already exists, this will overwrite with a new value.
285
   *
286
   * @param delimited The variable name.
287
   * @param dereferenced The resolved value.
288
   */
289
  private void put( String delimited, String dereferenced ) {
290
    if( dereferenced.isEmpty() ) {
291
      missing( delimited );
292
    } else {
293
      getReferences().put( delimited, dereferenced );
294
    }
295
  }
296
297
  /**
298
   * Writes the modified YAML document to standard output.
299
   */
300
  private void writeDocument() throws IOException {
301
    getObjectMapper().writeValue( System.out, getDocumentRoot() );
302
  }
303
304
  /**
305
   * Called when a delimited reference is dereferenced to an empty string. This
306
   * should produce a warning for the user.
307
   *
308
   * @param delimited Delimited reference with no derived value.
309
   */
310
  private void missing( final String delimited ) {
311
    throw new InvalidParameterException(
312
      MessageFormat.format( "Missing value for '{0}'.", delimited ) );
313
  }
314
315
  /**
316
   * Returns a REGEX_PATTERN matcher for the given text.
317
   *
318
   * @param text The text that contains zero or more instances of a
319
   * REGEX_PATTERN that can be found using the regular expression.
320
   */
321
  private Matcher patternMatch( String text ) {
322
    return getPattern().matcher( text );
323
  }
324
325
  /**
326
   * Finds the YAML value for a reference.
327
   *
328
   * @param reference References a value in the YAML document.
329
   *
330
   * @return The dereferenced value.
331
   */
332
  private String lookup( final String reference ) {
333
    return getDocumentRoot().at( asPath( reference ) ).asText();
334
  }
335
336
  /**
337
   * Converts a reference (not delimited) to a path that can be used to find a
338
   * value that should exist inside the YAML document.
339
   *
340
   * @param reference The reference to convert to a YAML document path.
341
   *
342
   * @return The reference with a leading slash and its separator characters
343
   * converted to slashes.
344
   */
345
  private String asPath( final String reference ) {
346
    return SEPARATOR_YAML + reference.replace( getDelimitedSeparator(), SEPARATOR_YAML );
347
  }
348
349
  /**
350
   * Sets the parent node for the entire YAML document tree.
351
   *
352
   * @param documentRoot The parent node.
353
   */
354
  private void setDocumentRoot( ObjectNode documentRoot ) {
355
    this.documentRoot = documentRoot;
356
  }
357
358
  /**
359
   * Returns the parent node for the entire YAML document tree.
360
   *
361
   * @return The parent node.
362
   */
363
  private ObjectNode getDocumentRoot() {
364
    return this.documentRoot;
365
  }
366
367
  /**
368
   * Returns the compiled regular expression REGEX_PATTERN used to match
369
   * delimited references.
370
   *
371
   * @return A compiled regex for use with the Matcher.
372
   */
373
  private Pattern getPattern() {
374
    return REGEX_PATTERN;
375
  }
376
377
  /**
378
   * Returns the list of references mapped to dereferenced values.
379
   *
380
   * @return
381
   */
382
  private Map<String, String> getReferences() {
383
    if( this.references == null ) {
384
      this.references = createReferences();
385
    }
386
387
    return this.references;
388
  }
389
390
  /**
391
   * Subclasses can override this method to insert their own map.
392
   *
393
   * @return An empty HashMap, never null.
394
   */
395
  protected Map<String, String> createReferences() {
396
    return new HashMap<>();
397
  }
398
399
  private class ResolverYAMLFactory extends YAMLFactory {
400
401
    @Override
402
    protected YAMLGenerator _createGenerator(
403
      final Writer out, final IOContext ctxt ) throws IOException {
404
405
      return new ResolverYAMLGenerator(
406
        ctxt, _generatorFeatures, _yamlGeneratorFeatures, _objectCodec,
407
        out, _version );
408
    }
409
  }
410
411
  private class ResolverYAMLGenerator extends YAMLGenerator {
412
413
    public ResolverYAMLGenerator(
414
      final IOContext ctxt,
415
      final int jsonFeatures,
416
      final int yamlFeatures,
417
      final ObjectCodec codec,
418
      final Writer out,
419
      final DumperOptions.Version version ) throws IOException {
420
421
      super( ctxt, jsonFeatures, yamlFeatures, codec, out, version );
422
    }
423
424
    @Override
425
    public void writeString( final String text )
426
      throws IOException, JsonGenerationException {
427
      super.writeString( substitute( text ) );
428
    }
429
  }
430
431
  private YAMLFactory getYAMLFactory() {
432
    return new ResolverYAMLFactory();
433
  }
434
435
  private ObjectMapper getObjectMapper() {
436
    return new ObjectMapper( getYAMLFactory() );
437
  }
438
439
  /**
440
   * Returns the character used to separate YAML paths within delimited
441
   * references. This will return only the first character of the command line
442
   * parameter, if the default is overridden.
443
   *
444
   * @return A period by default.
445
   */
446
  private char getDelimitedSeparator() {
447
    return SEPARATOR.charAt( 0 );
448
  }
449
}
1450
A src/main/java/com/scrivenvar/yaml/YamlTreeAdapter.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.yaml;
29
30
import com.fasterxml.jackson.databind.JsonNode;
31
import com.scrivenvar.ui.VariableTreeItem;
32
import java.io.IOException;
33
import java.io.InputStream;
34
import java.util.Map.Entry;
35
import javafx.scene.control.TreeItem;
36
import javafx.scene.control.TreeView;
37
38
/**
39
 * Transforms a JsonNode hierarchy into a tree that can be displayed in a user
40
 * interface.
41
 *
42
 * @author White Magic Software, Ltd.
43
 */
44
public class YamlTreeAdapter {
45
46
  private YamlParser yamlParser;
47
48
  public YamlTreeAdapter( final YamlParser parser ) {
49
    setYamlParser( parser );
50
  }
51
52
  /**
53
   * Converts a YAML document to a TreeView based on the document keys. Only the
54
   * first document in the stream is adapted. This does not close the stream.
55
   *
56
   * @param in Contains a YAML document.
57
   * @param name Name of the root TreeItem.
58
   *
59
   * @return A TreeView populated with all the keys in the YAML document.
60
   *
61
   * @throws IOException Could not read from the stream.
62
   */
63
  public TreeView<String> adapt( final InputStream in, final String name )
64
    throws IOException {
65
66
    final JsonNode rootNode = getYamlParser().process( in );
67
    final TreeItem<String> rootItem = createTreeItem( name );
68
69
    rootItem.setExpanded( true );
70
    adapt( rootNode, rootItem );
71
    return new TreeView<>( rootItem );
72
  }
73
74
  /**
75
   * Iterate over a given root node (at any level of the tree) and adapt each
76
   * leaf node.
77
   *
78
   * @param rootNode A JSON node (YAML node) to adapt.
79
   * @param rootItem The tree item to use as the root when processing the node.
80
   */
81
  private void adapt(
82
    final JsonNode rootNode, final TreeItem<String> rootItem ) {
83
84
    rootNode.fields().forEachRemaining(
85
      (Entry<String, JsonNode> leaf) -> adapt( leaf, rootItem )
86
    );
87
  }
88
89
  /**
90
   * Recursively adapt each rootNode to a corresponding rootItem.
91
   *
92
   * @param rootNode The node to adapt.
93
   * @param rootItem The item to adapt using the node's key.
94
   */
95
  private void adapt(
96
    final Entry<String, JsonNode> rootNode, final TreeItem<String> rootItem ) {
97
98
    final JsonNode leafNode = rootNode.getValue();
99
    final String key = rootNode.getKey();
100
    final TreeItem<String> leaf = createTreeItem( key );
101
102
    if( leafNode.isValueNode() ) {
103
      leaf.getChildren().add( createTreeItem( rootNode.getValue().asText() ) );
104
    }
105
106
    rootItem.getChildren().add( leaf );
107
108
    if( leafNode.isObject() ) {
109
      adapt( leafNode, leaf );
110
    }
111
  }
112
113
  /**
114
   * Creates a new tree item that can be added to the tree view.
115
   *
116
   * @param value The node's value.
117
   *
118
   * @return A new tree item node, never null.
119
   */
120
  private TreeItem<String> createTreeItem( final String value ) {
121
    return new VariableTreeItem<>( value );
122
  }
123
124
  private YamlParser getYamlParser() {
125
    return this.yamlParser;
126
  }
127
128
  private void setYamlParser( final YamlParser yamlParser ) {
129
    this.yamlParser = yamlParser;
130
  }
131
132
}
1133
A src/main/resources/META-INF/services/com.scrivenvar.service.Options
1
1
com.scrivenvar.service.impl.DefaultOptions
A src/main/resources/META-INF/services/com.scrivenvar.service.Settings
1
1
com.scrivenvar.service.impl.DefaultSettings
A src/main/resources/META-INF/services/com.scrivenvar.service.events.AlertService
1
1
com.scrivenvar.service.events.impl.DefaultAlertService
A src/main/resources/com/scrivenvar/build.sh
1
#!/bin/bash
2
3
INKSCAPE=/usr/bin/inkscape
4
5
declare -a SIZES=("16" "32" "64" "128" "256" "512")
6
7
for i in "${SIZES[@]}"; do
8
  # -y: export background opacity 0
9
  $INKSCAPE -y 0 -z -f "logo.svg" -w "${i}" -e "logo${i}.png"
10
done
11
112
A src/main/resources/com/scrivenvar/editor/Markdown.css
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
.markdown-editor {
29
  -fx-font-size: 14px;
30
}
31
32
/*---- headers ----*/
33
34
.markdown-editor .h1 { -fx-font-size: 2.25em; }
35
.markdown-editor .h2 { -fx-font-size: 1.75em; }
36
.markdown-editor .h3 { -fx-font-size: 1.5em; }
37
.markdown-editor .h4 { -fx-font-size: 1.25em; }
38
.markdown-editor .h5 { -fx-font-size: 1.1em; }
39
.markdown-editor .h6 { -fx-font-size: 1em; }
40
41
.markdown-editor .h1,
42
.markdown-editor .h2,
43
.markdown-editor .h3,
44
.markdown-editor .h4,
45
.markdown-editor .h5,
46
.markdown-editor .h6 {
47
  -fx-font-weight: bold;
48
  -fx-fill: derive(crimson, -20%);
49
}
50
51
52
/*---- inlines ----*/
53
54
.markdown-editor .strong {
55
  -fx-font-weight: bold;
56
}
57
58
.markdown-editor .em {
59
  -fx-font-style: italic;
60
}
61
62
.markdown-editor .del {
63
  -fx-strikethrough: true;
64
}
65
66
.markdown-editor .a {
67
  -fx-fill: #4183C4 !important;
68
}
69
70
.markdown-editor .img {
71
  -fx-fill: #4183C4 !important;
72
}
73
74
.markdown-editor .code {
75
  -fx-font-family: monospace;
76
  -fx-fill: #090 !important;
77
}
78
79
80
/*---- blocks ----*/
81
82
.markdown-editor .pre {
83
  -fx-font-family: monospace;
84
  -fx-fill: #060 !important;
85
}
86
87
.markdown-editor .blockquote {
88
  -fx-fill: #777;
89
}
90
91
92
/*---- lists ----*/
93
94
.markdown-editor .ul {
95
}
96
97
.markdown-editor .ol {
98
}
99
100
.markdown-editor .li {
101
  -fx-fill: #444;
102
}
103
104
.markdown-editor .dl {
105
}
106
107
.markdown-editor .dt {
108
  -fx-font-weight: bold;
109
  -fx-font-style: italic;
110
}
111
112
.markdown-editor .dd {
113
  -fx-fill: #444;
114
}
115
116
117
/*---- table ----*/
118
119
.markdown-editor .table {
120
  -fx-font-family: monospace;
121
}
122
123
.markdown-editor .thead {
124
}
125
126
.markdown-editor .tbody {
127
}
128
129
.markdown-editor .caption {
130
}
131
132
.markdown-editor .th {
133
  -fx-font-weight: bold;
134
}
135
136
.markdown-editor .tr {
137
}
138
139
.markdown-editor .td {
140
}
141
142
143
/*---- misc ----*/
144
145
.markdown-editor .html {
146
  -fx-font-family: monospace;
147
  -fx-fill: derive(crimson, -50%);
148
}
149
.markdown-editor .monospace {
150
  -fx-font-family: monospace;
151
}
1152
A src/main/resources/com/scrivenvar/logo.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4
<svg
5
   xmlns:dc="http://purl.org/dc/elements/1.1/"
6
   xmlns:cc="http://creativecommons.org/ns#"
7
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8
   xmlns:svg="http://www.w3.org/2000/svg"
9
   xmlns="http://www.w3.org/2000/svg"
10
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
   id="svg2"
13
   version="1.1"
14
   inkscape:version="0.91 r13725"
15
   width="512"
16
   height="512"
17
   viewBox="0 0 512 512"
18
   sodipodi:docname="logo.svg">
19
  <metadata
20
     id="metadata8">
21
    <rdf:RDF>
22
      <cc:Work
23
         rdf:about="">
24
        <dc:format>image/svg+xml</dc:format>
25
        <dc:type
26
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
27
        <dc:title></dc:title>
28
      </cc:Work>
29
    </rdf:RDF>
30
  </metadata>
31
  <defs
32
     id="defs6" />
33
  <sodipodi:namedview
34
     pagecolor="#ffffff"
35
     bordercolor="#666666"
36
     borderopacity="1"
37
     objecttolerance="10"
38
     gridtolerance="10"
39
     guidetolerance="10"
40
     inkscape:pageopacity="0"
41
     inkscape:pageshadow="2"
42
     inkscape:window-width="640"
43
     inkscape:window-height="480"
44
     id="namedview4"
45
     showgrid="false"
46
     fit-margin-top="0"
47
     fit-margin-left="0"
48
     fit-margin-right="0"
49
     fit-margin-bottom="0"
50
     inkscape:zoom="1.2682274"
51
     inkscape:cx="15.646213"
52
     inkscape:cy="213.34955"
53
     inkscape:current-layer="svg2" />
54
  <path
55
     style="fill:#ce6200;fill-opacity:1"
56
     d="m 203.2244,511.85078 c -60.01827,-1.2968 -121.688643,-6.5314 -192.436493,-16.334 -5.8078027,-0.8047 -10.66110747,-1.561 -10.78511762,-1.6806 -0.12404567,-0.1196 3.90488112,-4.5812 8.95313512,-9.9147 32.9484785,-34.8102 70.4314485,-73.8923 104.1521555,-108.5956 l 11.87611,-12.2221 5.48905,-10.2177 c 35.82801,-66.6927 75.13064,-128.5665 105.90637,-166.7277 6.13805,-7.611 10.21451,-12.0689 17.28719,-18.9048 36.6818,-35.4537 108.27279,-83.724003 206.0323,-138.917303 22.10365,-12.47935 51.93386,-28.64995037 52.26391,-28.33165037 0.38883,0.37499 -2.35932,25.95575037 -4.86585,45.29275037 -7.28943,56.236403 -17.04619,103.128903 -28.07642,134.939803 -7.19617,20.7536 -14.81287,35.152 -22.9667,43.4155 -3.60444,3.6529 -6.58328,5.7941 -10.1313,7.2825 l -2.56414,1.0756 -53.43164,0.1713 -53.43166,0.1713 3.69973,1.8547 c 26.78565,13.4282 52.58051,27.5241 59.57122,32.5533 4.48397,3.2259 4.41278,2.9854 1.59124,5.3784 -26.99514,22.8955 -74.52961,44.0013 -140.23089,62.2641 -26.34995,7.3244 -57.85469,14.6842 -86.99871,20.3237 l -10.26943,1.9871 -52.01052,53.2733 -52.010524,53.2732 -29.459801,15.1165 c -26.4100885,13.5517 -29.3446639,15.1388 -28.347645,15.3311 0.6117029,0.118 4.0894221,0.2188 7.7282726,0.2239 3.6388854,0.01 16.1273694,0.2329 27.7522124,0.5059 51.576376,1.2116 146.083985,1.512 170.154295,0.5409 34.66996,-1.3988 52.7606,-2.9325 67.58258,-5.7293 2.68664,-0.507 4.82907,-0.9755 4.76094,-1.0412 -0.0681,-0.066 -3.24733,-0.8833 -7.0649,-1.8169 -8.04133,-1.9664 -25.10167,-5.3107 -41.1231,-8.0612 -47.6405,-8.1787 -65.48708,-12.0107 -74.13028,-15.9169 -3.90548,-1.7651 -7.13816,-4.7659 -8.12937,-7.5463 -1.01822,-2.8562 -0.92214,-6.5271 0.23315,-8.9083 1.86563,-3.8451 6.14837,-6.7199 12.26745,-8.2345 16.96993,-4.2004 57.27977,-6.1832 90.36228,-4.4448 54.7332,2.8761 117.0767,13.1228 178.50212,29.3385 18.03514,4.7611 51.66065,14.656 51.22677,15.0744 -0.0824,0.08 -5.72762,-0.854 -12.54488,-2.0745 -40.1043,-7.18 -60.50854,-10.2888 -101.40822,-15.4507 -24.4851,-3.0902 -55.12614,-5.9915 -77.58876,-7.3465 -26.58826,-1.6039 -61.15821,-1.7754 -80.99202,-0.4019 l -3.19705,0.2214 8.70308,1.4934 c 51.89698,8.9047 77.51746,14.9877 88.00479,20.8948 6.9134,3.894 10.30497,9.4381 9.33333,15.2569 -1.50397,9.0066 -10.51381,14.0257 -32.00273,17.8278 -16.31374,2.8863 -47.27575,4.3845 -77.23553,3.7371 z"
57
     id="path4138" />
58
  <path
59
     style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
60
     d="m 214.76931,324.51908 c 60.83777,-14.1145 111.89562,-31.6251 144.40025,-49.5229 3.12602,-1.7213 5.81747,-3.2537 5.98106,-3.4054 0.40534,-0.3759 -13.76388,-7.9415 -34.63489,-18.4929 -7.52161,-3.8026 -9.82337,-5.3787 -12.0735,-8.2668 -5.14485,-6.6036 -5.96081,-14.8404 -2.20331,-22.2417 1.80288,-3.5512 5.69484,-7.3007 9.36158,-9.019 5.20851,-2.4407 1.18148,-2.2865 59.71223,-2.2865 l 52.81361,0 2.13233,-2.1984 c 2.78673,-2.8731 5.23414,-6.4981 8.23035,-12.1905 14.14966,-26.8827 26.71842,-78.3816 36.24347,-148.503303 0.76704,-5.6468 1.36194,-10.2983 1.32201,-10.3369 -0.0399,-0.038 -5.47754,2.9629 -12.08361,6.6697 l -12.01104,6.7396 -133.83068,137.037303 c -73.60688,75.3705 -134.81732,138.0567 -136.0232,139.3026 l -2.19251,2.2653 8.254,-1.8067 c 4.53969,-0.9937 12.01053,-2.6783 16.60185,-3.7435 z"
61
     id="path4136" />
62
  <path
63
     style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
64
     d="m 202.72524,284.43588 c 69.93294,-70.1332 135.4799,-131.9279 213.46406,-201.244203 7.71421,-6.8568 14.50542,-12.9341 15.09155,-13.5052 0.9482,-0.9239 0.96778,-0.9811 0.17761,-0.5188 -77.96496,45.611803 -139.23519,88.710503 -166.72539,117.278203 -18.81811,19.5556 -50.35654,64.861 -80.96704,116.3104 -0.91787,1.5427 1.02249,-0.3323 18.95921,-18.3204 z"
65
     id="path4142" />
66
  <path
67
     style="fill:#000000"
68
     d=""
69
     id="path4140"
70
     inkscape:connector-curvature="0" />
71
</svg>
172
A src/main/resources/com/scrivenvar/logo128.png
Binary file
A src/main/resources/com/scrivenvar/logo16.png
Binary file
A src/main/resources/com/scrivenvar/logo256.png
Binary file
A src/main/resources/com/scrivenvar/logo32.png
Binary file
A src/main/resources/com/scrivenvar/logo512.png
Binary file
A src/main/resources/com/scrivenvar/logo64.png
Binary file
A src/main/resources/com/scrivenvar/messages.properties
1
#
2
# Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
#
4
# All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are met:
8
#
9
#  * Redistributions of source code must retain the above copyright
10
#    notice, this list of conditions and the following disclaimer.
11
#
12
#  * Redistributions in binary form must reproduce the above copyright
13
#    notice, this list of conditions and the following disclaimer in the
14
#    documentation and/or other materials provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
#
28
29
# ########################################################################
30
#
31
# Main Application Window
32
#
33
# ########################################################################
34
35
# The application title should exist only once in the entire code base.
36
# All other references should either refer to this value via the Messages
37
# class, or indirectly using ${Main.title}.
38
Main.title=Scrivenvar
39
40
Main.menu.file=_File
41
Main.menu.file.new=New
42
Main.menu.file.open=Open...
43
Main.menu.file.close=Close
44
Main.menu.file.close_all=Close All
45
Main.menu.file.save=Save
46
Main.menu.file.save_all=Save All
47
Main.menu.file.exit=Exit
48
49
Main.menu.edit=_Edit
50
Main.menu.edit.undo=Undo
51
Main.menu.edit.redo=Redo
52
53
Main.menu.insert=_Insert
54
Main.menu.insert.bold=Bold
55
Main.menu.insert.italic=Italic
56
Main.menu.insert.strikethrough=Strikethrough
57
Main.menu.insert.blockquote=Blockquote
58
Main.menu.insert.code=Inline Code
59
Main.menu.insert.fenced_code_block=Fenced Code Block
60
Main.menu.insert.fenced_code_block.prompt=Enter code here
61
Main.menu.insert.link=Link...
62
Main.menu.insert.image=Image...
63
Main.menu.insert.header_1=Header 1
64
Main.menu.insert.header_1.prompt=header 1
65
Main.menu.insert.header_2=Header 2
66
Main.menu.insert.header_2.prompt=header 2
67
Main.menu.insert.header_3=Header 3
68
Main.menu.insert.header_3.prompt=header 3
69
Main.menu.insert.header_4=Header 4
70
Main.menu.insert.header_4.prompt=header 4
71
Main.menu.insert.header_5=Header 5
72
Main.menu.insert.header_5.prompt=header 5
73
Main.menu.insert.header_6=Header 6
74
Main.menu.insert.header_6.prompt=header 6
75
Main.menu.insert.unordered_list=Unordered List
76
Main.menu.insert.ordered_list=Ordered List
77
Main.menu.insert.horizontal_rule=Horizontal Rule
78
79
Main.menu.tools=_Tools
80
Main.menu.tools.options=Options
81
82
Main.menu.help=_Help
83
Main.menu.help.about=About ${Main.title}
84
85
# ########################################################################
86
#
87
# About Dialog
88
#
89
# ########################################################################
90
91
Dialog.about.title=About
92
Dialog.about.header=${Main.title}
93
Dialog.about.content=Copyright 2016 White Magic Software, Ltd.\n\nBased on Markdown Writer FX by Karl Tauber
94
95
# ########################################################################
96
#
97
# File Editor
98
#
99
# ########################################################################
100
101
FileEditor.untitled=Untitled
102
FileEditor.loadFailed.message=Failed to load ''{0}''.\n\nReason: {1}
103
FileEditor.loadFailed.title=Load
104
FileEditor.saveFailed.message=Failed to save ''{0}''.\n\nReason: {1}
105
FileEditor.saveFailed.title=Save
106
107
# ########################################################################
108
#
109
# File Open
110
#
111
# ########################################################################
112
113
Dialog.file.choose.open.title=Open File
114
Dialog.file.choose.save.title=Save File
115
116
Dialog.file.choose.filter.title.markdown=Markdown Files
117
Dialog.file.choose.filter.title.definition=Definition Files
118
Dialog.file.choose.filter.title.xml=XML Files
119
Dialog.file.choose.filter.title.all=All Files
120
121
# ########################################################################
122
#
123
# Alert Dialog
124
#
125
# ########################################################################
126
127
Alert.file.close.title=Close
128
Alert.file.close.text=Save changes to {0}?
129
130
# ########################################################################
131
#
132
# Definition Pane
133
#
134
# ########################################################################
135
136
Pane.defintion.node.root.title=Definitions
137
138
# Controls ###############################################################
139
140
# ########################################################################
141
#
142
# Browse Directory
143
#
144
# ########################################################################
145
146
BrowseDirectoryButton.chooser.title=Browse for local folder
147
BrowseDirectoryButton.tooltip=${BrowseDirectoryButton.chooser.title}
148
149
# ########################################################################
150
#
151
# Browse File
152
#
153
# ########################################################################
154
155
BrowseFileButton.chooser.title=Browse for local file
156
BrowseFileButton.chooser.allFilesFilter=All Files
157
BrowseFileButton.tooltip=${BrowseFileButton.chooser.title}
158
159
# Dialogs ################################################################
160
161
# ########################################################################
162
#
163
# Image
164
#
165
# ########################################################################
166
167
ImageDialog.title=Image
168
ImageDialog.chooser.imagesFilter=Images
169
ImageDialog.previewLabel.text=Markdown Preview\:
170
ImageDialog.textLabel.text=Alternate Text\:
171
ImageDialog.titleLabel.text=Title (tooltip)\:
172
ImageDialog.urlLabel.text=Image URL\:
173
174
# ########################################################################
175
#
176
# Hyperlink
177
#
178
# ########################################################################
179
180
LinkDialog.title=Link
181
LinkDialog.previewLabel.text=Markdown Preview\:
182
LinkDialog.textLabel.text=Link Text\:
183
LinkDialog.titleLabel.text=Title (tooltip)\:
184
LinkDialog.urlLabel.text=Link URL\:
185
186
# Options ################################################################
187
188
# ########################################################################
189
#
190
# Options Dialog
191
#
192
# ########################################################################
193
194
OptionsDialog.title=Options
195
OptionsDialog.generalTab.text=General
196
OptionsDialog.markdownTab.text=Markdown
197
198
# ########################################################################
199
#
200
# General Options Pane
201
#
202
# ########################################################################
203
204
GeneralOptionsPane.encodingLabel.text=En_coding\:
205
GeneralOptionsPane.lineSeparatorLabel.text=_Line separator\:
206
GeneralOptionsPane.lineSeparatorLabel2.text=(applies to new files only)
207
208
GeneralOptionsPane.platformDefault=Platform Default ({0})
209
GeneralOptionsPane.sepWindows=Windows (CRLF)
210
GeneralOptionsPane.sepUnix=Unix (LF)
211
212
# ########################################################################
213
#
214
# Markdown Options Pane
215
#
216
# ########################################################################
217
218
MarkdownOptionsPane.abbreviationsExtCheckBox.text=A_bbreviations in the way of
219
MarkdownOptionsPane.abbreviationsExtLink.text=Markdown Extra
220
MarkdownOptionsPane.anchorlinksExtCheckBox.text=_Anchor links in headers
221
MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text=Requires a space char after Atx \# header prefixes, so that \#dasdsdaf is not a header
222
MarkdownOptionsPane.autolinksExtCheckBox.text=_Plain (undelimited) autolinks in the way of
223
MarkdownOptionsPane.autolinksExtLink.text=Github-flavoured-Markdown
224
MarkdownOptionsPane.definitionListsExtCheckBox.text=_Definition lists in the way of
225
MarkdownOptionsPane.definitionListsExtLink.text=Markdown Extra
226
MarkdownOptionsPane.extAnchorLinksExtCheckBox.text=Generate anchor links for headers using complete contents of the header
227
MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text=_Fenced Code Blocks in the way of
228
MarkdownOptionsPane.fencedCodeBlocksExtLabel.text=or
229
MarkdownOptionsPane.fencedCodeBlocksExtLink.text=Markdown Extra
230
MarkdownOptionsPane.fencedCodeBlocksExtLink2.text=Github-flavoured-Markdown
231
MarkdownOptionsPane.forceListItemParaExtCheckBox.text=Force List and Definition Paragraph wrapping if it includes more than just a single paragraph
232
MarkdownOptionsPane.hardwrapsExtCheckBox.text=_Newlines in paragraph-like content as real line breaks, see
233
MarkdownOptionsPane.hardwrapsExtLink.text=Github-flavoured-Markdown
234
MarkdownOptionsPane.quotesExtCheckBox.text=Beautify single _quotes, double quotes and double angle quotes (\u00ab and \u00bb)
235
MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text=Allow horizontal rules without a blank line following them
236
MarkdownOptionsPane.smartsExtCheckBox.text=Beautify apostrophes, _ellipses ("..." and ". . .") and dashes ("--" and "---")
237
MarkdownOptionsPane.strikethroughExtCheckBox.text=_Strikethrough
238
MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text=Suppress the _output of HTML blocks
239
MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text=Suppress the o_utput of inline HTML elements
240
MarkdownOptionsPane.tablesExtCheckBox.text=_Tables similar to
241
MarkdownOptionsPane.tablesExtLabel.text=(like
242
MarkdownOptionsPane.tablesExtLabel2.text=tables, but with colspan support)
243
MarkdownOptionsPane.tablesExtLink.text=MultiMarkdown
244
MarkdownOptionsPane.tablesExtLink2.text=Markdown Extra
245
MarkdownOptionsPane.taskListItemsExtCheckBox.text=GitHub style task list items
246
MarkdownOptionsPane.wikilinksExtCheckBox.text=_Wiki-style links ("[[wiki link]]")
1247
A src/main/resources/com/scrivenvar/preview/webview.css
1
/*
2
This software is released under the MIT license:
3
4
Permission is hereby granted, free of charge, to any person obtaining a copy of
5
this software and associated documentation files (the "Software"), to deal in
6
the Software without restriction, including without limitation the rights to
7
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
the Software, and to permit persons to whom the Software is furnished to do so,
9
subject to the following conditions:
10
11
The above copyright notice and this permission notice shall be included in all
12
copies or substantial portions of the Software.
13
14
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
*/
21
22
/* Source: https://github.com/nicolashery/markdownpad-github */
23
24
/*  GitHub stylesheet for MarkdownPad (http://markdownpad.com) */
25
26
/* RESET
27
=============================================================================*/
28
29
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
30
  margin: 0;
31
  padding: 0;
32
  border: 0;
33
}
34
35
/* BODY
36
=============================================================================*/
37
38
body {
39
  font-family: Helvetica, arial, freesans, clean, sans-serif;
40
  font-size: 14px;
41
  line-height: 1.6;
42
  color: #333;
43
  background-color: #fff;
44
  padding: 20px;
45
  max-width: 960px;
46
  margin: 0 auto;
47
}
48
49
body>*:first-child {
50
  margin-top: 0 !important;
51
}
52
53
body>*:last-child {
54
  margin-bottom: 0 !important;
55
}
56
57
/* BLOCKS
58
=============================================================================*/
59
60
p, blockquote, ul, ol, dl, table, pre {
61
  margin: 15px 0;
62
}
63
64
/* HEADERS
65
=============================================================================*/
66
67
h1, h2, h3, h4, h5, h6 {
68
  margin: 20px 0 10px;
69
  padding: 0;
70
  font-weight: bold;
71
  -webkit-font-smoothing: antialiased;
72
}
73
74
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
75
  font-size: inherit;
76
}
77
78
h1 {
79
  font-size: 28px;
80
  color: #000;
81
}
82
83
h2 {
84
  font-size: 24px;
85
  border-bottom: 1px solid #ccc;
86
  color: #000;
87
}
88
89
h3 {
90
  font-size: 18px;
91
}
92
93
h4 {
94
  font-size: 16px;
95
}
96
97
h5 {
98
  font-size: 14px;
99
}
100
101
h6 {
102
  color: #777;
103
  font-size: 14px;
104
}
105
106
body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
107
  margin-top: 0;
108
  padding-top: 0;
109
}
110
111
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
112
  margin-top: 0;
113
  padding-top: 0;
114
}
115
116
h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
117
  margin-top: 10px;
118
}
119
120
/* LINKS
121
=============================================================================*/
122
123
a {
124
  color: #4183C4;
125
  text-decoration: none;
126
}
127
128
a:hover {
129
  text-decoration: underline;
130
}
131
132
/* LISTS
133
=============================================================================*/
134
135
ul, ol {
136
  padding-left: 30px;
137
}
138
139
ul li > :first-child, 
140
ol li > :first-child, 
141
ul li ul:first-of-type, 
142
ol li ol:first-of-type, 
143
ul li ol:first-of-type, 
144
ol li ul:first-of-type {
145
  margin-top: 0px;
146
}
147
148
ul ul, ul ol, ol ol, ol ul {
149
  margin-bottom: 0;
150
}
151
152
dl {
153
  padding: 0;
154
}
155
156
dl dt {
157
  font-size: 14px;
158
  font-weight: bold;
159
  font-style: italic;
160
  padding: 0;
161
  margin: 15px 0 5px;
162
}
163
164
dl dt:first-child {
165
  padding: 0;
166
}
167
168
dl dt>:first-child {
169
  margin-top: 0px;
170
}
171
172
dl dt>:last-child {
173
  margin-bottom: 0px;
174
}
175
176
dl dd {
177
  margin: 0 0 15px;
178
  padding: 0 15px;
179
}
180
181
dl dd>:first-child {
182
  margin-top: 0px;
183
}
184
185
dl dd>:last-child {
186
  margin-bottom: 0px;
187
}
188
189
/* CODE
190
=============================================================================*/
191
192
pre, code, tt {
193
  font-size: 12px;
194
  font-family: Consolas, "Liberation Mono", Courier, monospace;
195
}
196
197
code, tt {
198
  margin: 0 0px;
199
  padding: 0px 0px;
200
  white-space: nowrap;
201
  border: 1px solid #eaeaea;
202
  background-color: #f8f8f8;
203
  border-radius: 3px;
204
}
205
206
pre>code {
207
  margin: 0;
208
  padding: 0;
209
  white-space: pre;
210
  border: none;
211
  background: transparent;
212
}
213
214
pre {
215
  background-color: #f8f8f8;
216
  border: 1px solid #ccc;
217
  font-size: 13px;
218
  line-height: 19px;
219
  overflow: auto;
220
  padding: 6px 10px;
221
  border-radius: 3px;
222
}
223
224
pre code, pre tt {
225
  background-color: transparent;
226
  border: none;
227
}
228
229
kbd {
230
    -moz-border-bottom-colors: none;
231
    -moz-border-left-colors: none;
232
    -moz-border-right-colors: none;
233
    -moz-border-top-colors: none;
234
    background-color: #DDDDDD;
235
    background-image: linear-gradient(#F1F1F1, #DDDDDD);
236
    background-repeat: repeat-x;
237
    border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
238
    border-image: none;
239
    border-radius: 2px 2px 2px 2px;
240
    border-style: solid;
241
    border-width: 1px;
242
    font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
243
    line-height: 10px;
244
    padding: 1px 4px;
245
}
246
247
/* QUOTES
248
=============================================================================*/
249
250
blockquote {
251
  border-left: 4px solid #DDD;
252
  padding: 0 15px;
253
  color: #777;
254
}
255
256
blockquote>:first-child {
257
  margin-top: 0px;
258
}
259
260
blockquote>:last-child {
261
  margin-bottom: 0px;
262
}
263
264
/* HORIZONTAL RULES
265
=============================================================================*/
266
267
hr {
268
  clear: both;
269
  margin: 15px 0;
270
  height: 0px;
271
  overflow: hidden;
272
  border: none;
273
  background: transparent;
274
  border-bottom: 4px solid #ddd;
275
  padding: 0;
276
}
277
278
/* TABLES
279
=============================================================================*/
280
281
table th {
282
  font-weight: bold;
283
}
284
285
table th, table td {
286
  border: 1px solid #ccc;
287
  padding: 6px 13px;
288
}
289
290
table tr {
291
  border-top: 1px solid #ccc;
292
  background-color: #fff;
293
}
294
295
table tr:nth-child(2n) {
296
  background-color: #f8f8f8;
297
}
298
299
/* IMAGES
300
=============================================================================*/
301
302
img {
303
  max-width: 100%
304
}
1305
A src/main/resources/com/scrivenvar/scene.css
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
/*---- toolbar ----*/
29
30
.tool-bar {
31
	-fx-spacing: 0;
32
}
33
34
.tool-bar .button {
35
	-fx-background-color: transparent;
36
}
37
38
.tool-bar .button:hover {
39
	-fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
40
	-fx-color: -fx-hover-base;
41
}
42
43
.tool-bar .button:armed {
44
	-fx-color: -fx-pressed-base;
45
}
146
A src/main/resources/com/scrivenvar/settings.properties
1
# ########################################################################
2
#
3
# Filename Extensions
4
#
5
# ########################################################################
6
7
# Comma-separated list of filename extensions.
8
Dialog.file.choose.filter.ext.markdown=*.Rmd,*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt
9
Dialog.file.choose.filter.ext.definition=*.yml,*.yaml,*.properties,*.props
10
Dialog.file.choose.filter.ext.xml=*.xml,*.Rxml
11
Dialog.file.choose.filter.ext.all=*.*
12
13
# ########################################################################
14
#
15
# Variable Name Editor
16
#
17
# ########################################################################
18
19
# Maximum number of characters for a variable name. A variable is defined
20
# as one or more non-whitespace characters up to this maximum length.
21
editor.variable.maxLength=256
22
23
# ########################################################################
24
#
25
# Dialog Preferences
26
#
27
# ########################################################################
28
29
# docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ButtonBar.html
30
dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R
31
dialog.alert.button.order.linux=L_HE+UNYACBXIO_R
32
dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R
33
34
# Ensures a consistent button order for alert dialogs across platforms (because
35
# the default button order on Linux defies all logic). Power to the people.
36
dialog.alert.button.order=${dialog.alert.button.order.windows}
137
A src/main/resources/com/scrivenvar/variables.yaml
1
---
2
c:
3
  protagonist:
4
    name:
5
      First: Chloe
6
      First_pos: $c.protagonist.name.First$'s
7
      Middle: Irene
8
      Family: Angelos
9
      nick:
10
        Father: Savant
11
        Mother: Sweetie
12
    colour:
13
      eyes: green
14
      hair: dark auburn
15
      syn_1: black
16
      syn_2: purple
17
      syn_11: teal
18
      syn_6: silver
19
      favourite: emerald green
20
    speech:
21
      tic: oh
22
    father:
23
      heritage: Greek
24
      name:
25
        Short: Bryce
26
        First: Bryson
27
        First_pos: $c.protagonist.father.name.First$'s
28
        Honourific: Mr.
29
      education: Masters
30
      vocation:
31
        name: robotics
32
        title: roboticist
33
      employer:
34
        name:
35
          Short: Rabota
36
          Full: $c.protagonist.father.employer.name.Short$ Designs
37
      hair:
38
        style: thick, curly
39
        colour: black
40
      eyes:
41
        colour: dark brown
42
      Endear: Dad
43
      vehicle: coupé
44
    mother:
45
      name:
46
        Short: Cass
47
        First: Cassandra
48
        First_pos: $c.protagonist.mother.name.First$'s
49
        Honourific: Mrs.
50
      education: PhD
51
      speech:
52
        tic: cute
53
        Honorific: Doctor
54
      vocation:
55
        article: an
56
        name: oceanography
57
        title: oceanographer
58
      employer:
59
        name:
60
          Full: Oregon State University
61
          Short: OSU
62
      eyes:
63
        colour: blue
64
      hair:
65
        style: thick, curly
66
        colour: dark brown
67
      Endear: Mom
68
      Endear_pos: Mom's
69
    uncle:
70
      name:
71
        First: Damian
72
        First_pos: $c.protagonist.uncle.name.First$'s
73
        Family: Moros
74
      hands:
75
        fingers:
76
          shape: long, bony
77
    friend:
78
      primary:
79
        name:
80
          First: Gerard
81
          First_pos: $c.protagonist.friend.primary.name.First$'s
82
          Family: Baran
83
          Family_pos: $c.protagonist.friend.primary.name.Family$'s
84
        favourite:
85
          colour: midnight blue
86
        eyes:
87
          colour: hazel
88
        mother:
89
          name:
90
            First: Isabella
91
            Short: Izzy
92
            Honourific: Mrs.
93
        father:
94
          name:
95
            Short: Mo
96
            First: Montgomery
97
            First_pos: $c.protagonist.friend.primary.father.name.First$'s
98
            Honourific: Mr.
99
          speech:
100
            tic: y'know
101
          endear: Pops
102
  military:
103
    primary:
104
      name:
105
        First: Felix
106
        Family: LeMay
107
        Family_pos: LeMay's
108
      rank:
109
        Short: General
110
        Full: Brigadier $c.military.primary.rank.Short$
111
      colour:
112
        eyes: gray
113
        hair: dirty brown
114
    secondary:
115
      name:
116
        Family: Grell
117
      rank: Colonel
118
      colour:
119
        eyes: green
120
        hair: deep red
121
    quaternary:
122
      name:
123
        First: Gretchen
124
        Family: Steinherz
125
  minor:
126
    primary:
127
      name:
128
        First: River
129
        Family: Banks
130
        Honourific: Mx.
131
      vocation:
132
        title: salesperson
133
      employer:
134
        Name: Geophysical Prospecting Incorporated
135
        Abbr: GPI
136
        Area: Cold Spring Creek
137
        payment: twenty million
138
    secondary:
139
      name:
140
        First: Renato
141
        Middle: Carroña
142
        Family: Salvatierra
143
        Family_pos: $c.minor.secondary.name.Family$'s
144
        Full: $c.minor.secondary.name.First$ $c.minor.secondary.name.Middle$ Alejandro Gregorio Eduardo Salomón Vidal $c.minor.secondary.name.Family$
145
        Honourific: Mister
146
        Honourific_sp: Señor
147
      vocation:
148
        title: detective
149
    tertiary:
150
      name:
151
        First: Robert
152
        Family: Hanssen
153
154
  ai:
155
    protagonist:
156
      name:
157
        first: yoky
158
        First: Yoky
159
        First_pos: $c.ai.protagonist.name.First$'s
160
        Family: Tsukuda
161
        id: 46692
162
      persona:
163
        name:
164
          First: Hoshi
165
          First_pos: $c.ai.protagonist.persona.name.First$'s
166
          Family: Yamamoto
167
          Family_pos: $c.ai.protagonist.persona.name.Family$'s
168
      culture: Japanese-American
169
      ethnicity: Asian
170
      rank: Technical Sergeant
171
      speech:
172
        tic: okay
173
    first:
174
      Name: Prôtos
175
      Name_pos: Prôtos'
176
      age:
177
        actual: twenty-six weeks
178
        virtual: five years
179
    second:
180
      Name: Défteros
181
    third:
182
      Name: Trítos
183
    fourth:
184
      Name: Tétartos
185
    material:
186
      type: metal
187
      raw: ilmenite
188
      extract: ore
189
      name:
190
        short: titanium
191
        long: $c.ai.material.name.short$ dioxide
192
        Abbr: TiO~2~
193
      pejorative: tin
194
  animal:
195
    protagonist:
196
      Name: Trufflers
197
      type: pig
198
    antagonist:
199
      name: coywolf
200
      Name: Coywolf
201
      plural: coywolves
202
203
narrator:
204
  one: (by $c.protagonist.father.name.First$ $c.protagonist.name.Family$)
205
  two: (by $c.protagonist.mother.name.First$ $c.protagonist.name.Family$)
206
207
military:
208
  name:
209
    Short: Agency
210
    Short_pos: $military.name.Short$'s
211
    plural: agencies
212
  machine:
213
    Name: Skopós
214
    Name_pos: $military.machine.Name$'
215
    Location: Arctic
216
    predictor: quantum chips
217
  land:
218
    name:
219
      Full: $military.name.Short$ of Defence
220
    Slogan: Safety in Numbers
221
  air:
222
    name:
223
      Full: $military.name.Short$ of Air
224
  compound:
225
    type: base
226
    lights:
227
      colour: blue
228
    nick:
229
      Prefix: Catacombs
230
      prep: of
231
      Suffix: Tartarus
232
233
government:
234
  Country: United States
235
236
location:
237
  protagonist:
238
    City: Corvallis
239
    Region: Oregon
240
    Geography: Willamette Valley
241
    secondary:
242
      City: Willow Branch Spring
243
      Region: Oregon
244
      Geography: Wheeler County
245
      Water: Clarno Rapids
246
      Road: Shaniko-Fossil Highway
247
    tertiary:
248
      City: Leavenworth
249
      Region: Washington
250
      Type: Bavarian village
251
    school:
252
      address: 1400 Northwest Buchanan Avenue
253
    hospital:
254
      Name: Good Samaritan Regional Medical Center
255
  ai:
256
    escape:
257
      country:
258
        Name: Ecuador
259
        Name_pos: Ecuador's
260
      mountain:
261
        Name: Chimborazo
262
263
language:
264
  ai:
265
    article: an
266
    singular: exanimis
267
    plural: exanimēs
268
    brain:
269
      singular: superum
270
      plural: supera
271
    title: memristor array
272
    Title: Memristor Array
273
  police:
274
    slang:
275
      singular: mippo
276
      plural: $language.police.slang.singular$s
277
278
date:
279
  anchor: 2042-09-02
280
  protagonist:
281
    born: 0
282
    conceived: -243
283
    attacked:
284
      first: 2192
285
      second: 8064
286
    father:
287
      attacked:
288
        first: -8205
289
      date:
290
        second: -1550
291
    family:
292
      moved:
293
        first: $date.protagonist.conceived$ + 35
294
  game:
295
    played:
296
      first: $date.protagonist.born$ - 672
297
      second: $date.protagonist.family.moved.first$ + 2
298
  ai:
299
    interviewed: 6198
300
    onboarded: $date.ai.interviewed$ + 290
301
    diagnosed: $date.ai.onboarded$ + 2
302
    resigned: $date.ai.diagnosed$ + 3
303
    trapped: $date.ai.resigned$ + 26
304
    torturer: $date.ai.trapped$ + 18
305
    memristor: $date.ai.torturer$ + 61
306
    ethics: $date.ai.memristor$ + 415
307
    trained: $date.ai.ethics$ + 385
308
    mindjacked: $date.ai.trained$ + 22
309
    bombed: $date.ai.mindjacked$ + 458
310
  military:
311
    machine:
312
      Construction: Six years
313
314
plot:
315
  Log: $c.ai.protagonist.name.First_pos$ Chronicles
316
  Channel: Quantum Channel
317
318
  device:
319
    computer:
320
      Name: Tau
321
    network:
322
      Name: Internet
323
    paper:
324
      name:
325
        full: electronic sheet
326
        short: sheet
327
    typewriter:
328
      Name: Underwood
329
      year: nineteen twenties
330
      room: root cellar
331
    portable:
332
      name: nanobook
333
    vehicle:
334
      name: robocars
335
      Name: Robocars
336
    sensor:
337
      name: BMP1580
338
    phone:
339
      name: comm
340
      name_pos: $plot.device.phone.name$'s
341
      Name: Comm
342
      plural: $plot.device.phone.name$s
343
    video:
344
      name: vidfeed
345
      plural: $plot.device.video.name$s
346
    game:
347
      Name: Psynæris
348
      thought: transed
349
      machine: telecognos
350
      location:
351
        Building: Nijō Castle
352
        District: Gion
353
        City: Kyoto
354
        Country: Japan
355
356
farm:
357
  population:
358
    estimate: 350
359
    actual: 1,000
360
  energy: 9800kJ
361
  width: 55m
362
  length: 55m
363
  storeys: 10
364
365
lamp:
366
  height: 0.17m
367
  length: 1.22m
368
  width: 0.28m
369
370
crop:
371
  name: 
372
    singular: tomato
373
    plural: $crop.name.singular$es
374
  energy: 318kJ
375
  weight: 450g
376
  yield: 50
377
  harvests: 7
378
  diameter: 2m
379
  height: 1.5m
380
381
heading:
382
  ch_01: Till
383
  ch_02: Sow
384
  ch_03: Seed
385
  ch_04: Germinate
386
  ch_05: Grow
387
  ch_06: Shoot
388
  ch_07: Bud
389
  ch_08: Bloom
390
  ch_09: Pollinate
391
  ch_10: Fruit
392
  ch_11: Harvest
393
  ch_12: Deliver
394
  ch_13: Spoil
395
  ch_14: Revolt
396
  ch_15: Compost
397
  ch_16: Burn
398
  ch_17: Release
399
  ch_18: End Notes
400
  ch_19: Characters
401
402
inference:
403
  unit: per cent
404
  min: two
405
  ch_sow: eighty
406
  ch_seed: fifty-two
407
  ch_germinate: thirty-one
408
  ch_grow: fifteen
409
  ch_shoot: seven
410
  ch_bloom: four
411
  ch_pollinate: two
412
  ch_harvest: ninety-five
413
  ch_delivery: ninety-eight
414
415
link:
416
  tartarus: https://en.wikipedia.org/wiki/Tartarus
417
  exploits: https://www.google.ca/search?q=inurl:ftp+password+filetype:xls
418
  atalanta: https://en.wikipedia.org/wiki/Atalanta
419
  detain: https://goo.gl/RCNuOQ
420
  ceramics: https://en.wikipedia.org/wiki/Transparent_ceramics
421
  algernon: https://en.wikipedia.org/wiki/Flowers_for_Algernon
422
  holocaust: https://en.wikipedia.org/wiki/IBM_and_the_Holocaust
423
  memristor: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.404.9037\&rep=rep1\&type=pdf
424
  surveillance: https://www.youtube.com/watch?v=XEVlyP4_11M#t=1487
425
  tor: https://www.torproject.org
426
  hydra: https://en.wikipedia.org/wiki/Lernaean_Hydra
427
  foliage: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3691134
428
  drake: http://www.bbc.com/future/story/20120821-how-many-alien-worlds-exist
429
  fermi: https://arxiv.org/pdf/1404.0204v1.pdf
430
  face: https://www.youtube.com/watch?v=ladqJQLR2bA
431
  expenditures: http://wikipedia.org/wiki/List_of_countries_by_military_expenditures
432
  governance: http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2003531
433
  asimov: https://en.wikipedia.org/wiki/Three_Laws_of_Robotics
434
  clarke: https://en.wikipedia.org/wiki/Clarke's_three_laws
435
  jetpack: http://jetpackaviation.com/
436
  hoverboard: https://www.youtube.com/watch?v=WQzLrvz4DKQ
437
  eyes_five: https://en.wikipedia.org/wiki/Five_Eyes
438
  eyes_nine: https://www.privacytools.io/
439
  eyes_fourteen: http://electrospaces.blogspot.nl/2013/12/14-eyes-are-3rd-party-partners-forming.html
440
  tourism: http://www.spacefuture.com/archive/investigation_on_the_economic_and_technological_feasibiity_of_commercial_passenger_transportation_into_leo.shtml
441
1442